Spring源码解析之DI篇(上)

144 阅读5分钟

什么是DI?

DI(Dependency Injection)依赖注入:当对象内包含对其他对象的引用时,Spring会帮我们创建或定义这些依赖对象,而无需知道依赖对象的位置甚至具体实现类,实现更有效的解耦。

既然已经知道什么是依赖注入,那么按照探究问题的准则,首先我们会想:依赖注入是什么时候开始的?又是怎么注入的?同时简单考虑下如果是自己会大概怎么去实现(不考虑细节)。比如我们知道IOC容器中保存了Bean对象,那么一个对象的属性依赖对象能不能直接从容器中拿然后赋值?根据容器中保存的beanName去拿?或者如果容器中没有,是不是要另外创建然后引用赋值过来?这样的话,是不是得先判断这个属性是不是Bean对象?还有如果新创建的对象中又有属性依赖其他对象怎么办?递归?等等。

不要觉得自己好像啥也想不出来或者肯定不对不敢随意猜,带着疑问去探究才会得到实际的印证,加深对本质的了解,而且你会更容易看的下去源码,所谓念念不忘,必有回响。

准备工作

接下来,我们先做一些准备工作,主要还是为了“眼熟”,提前熟悉一些解析源码时需要用到的知识点。

第一步:注入时机

也就是依赖注入是什么时候开始的?前一篇我们解析了Spring的IOC初始化流程,我们知道主要的流程在refresh()方法里面实现的,而解析加载注册主要发生在方法内的obtainFreshBeanFactory()中实现的,之后的代码中我们注意到finishBeanFactoryInitialization(beanFactory)这个方法,这个方法就是初始化所有剩余的单例Bean,点进去最后发现调用的是DefaultListableBeanFactory类中的preInstantiateSingletons()方法,我们看下源码,

	@Override
	public void preInstantiateSingletons() throws BeansException {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Pre-instantiating singletons in " + this);
		}
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			//Bean满足非抽象类,单例,lazy-init属性为false
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
					//是否预实例化
					boolean isEagerInit;
					if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
						isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>) () ->
								((SmartFactoryBean<?>) factory).isEagerInit(),
								getAccessControlContext());
					}
					else {
						isEagerInit = (factory instanceof SmartFactoryBean &&
								((SmartFactoryBean<?>) factory).isEagerInit());
					}
					if (isEagerInit) {
						//调用getBean方法,触发容器对Bean实例化和依赖注入过程
						getBean(beanName);
					}
				}
				else {
					getBean(beanName);
				}
			}
		}
		for (String beanName : beanNames) {
			Object singletonInstance = getSingleton(beanName);
			if (singletonInstance instanceof SmartInitializingSingleton) {
				final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
						smartSingleton.afterSingletonsInstantiated();
						return null;
					}, getAccessControlContext());
				}
				else {
                                        smartSingleton.afterSingletonsInstantiated();
				}
			}
		}
	}

这个方法是对配置lazy-init属性等于fasle的单例Bean的预实例化,我们看到到最后都调用了getBean()方法,它就是对Bean实例化和依赖注入过程,而具体更深入的探究我们后面会慢慢展开。

所以综上所述,我们知道了在IOC的初始化中其实包含了依赖注入的过程,同时通过对getBean()的延伸,进一步得到了第一个问题的答案,依赖注入的时机:

  • 非延迟加载的Bean: IOC初始化中对进行预实例化(也是调用getBean()方法)。
  • 延迟加载的Bean: 第一次调用getBean()方法时。

第二步:注入策略

Spring中注入策略有两种:

  • 有参构造方法注入: 带有参数的构造函数来完成实例化或者带参数的static工厂方法,因为需要对参数进行解析转换,所以过程复杂。
  • 无参构造方法注入: 默认实例化方法,例如使用setter方法注入,通过无参数构造函数或无参数的static工厂方法,过程简单。

第三步:自动装配方式

Spring官网中已经有详细的介绍,关于自动装配的方式有四种:

  • no:默认没有自动装配,必须由ref配置Bean的引用。
  • byName:根据属性名称在IOC容器中查找Bean,然后通过setter方法来注入属性(前提是必须有)。
  • byType:根据属性类型在IOC容器中查找Bean,只存在一个才会注入,多个会引发异常,没有则不注入。
  • constructor:根据构造函数的参数类型在IOC容器中查找Bean,没有则引发异常。

但是自动装配也有一些局限性和缺点,比如不能装配简单属性、不如显示装配准确、多个Bean会引发异常等,这些都在官方文档中有更详细的介绍。

第四步:关键类和属性

  • 缓存的属性: 后面我们解析源码的时候会经常看到一些存储Bean的不同的数据结构,Map或Set,我们先熟悉下它们的用处:

    • Map<String, Object> singletonObjects:保存beanName和Bean实例之间的关系。
    • Map<String, ObjectFactory<?>> singletonFactories:保存beanName和Bean工厂之间的关系。
    • Map<String, Object> earlySingletonObjects:保存beanName和Bean实例之间的关系,和singletonObjects不同的是,一个单例Bean还未创建完成前就被存放进去,且能通过getBean()方法拿到,主要目的是用于检查循环依赖。
    • Set registeredSingletons:保存当前所有已注册的Bean的beanName。
    • Set singletonsCurrentlyInCreation:保存当前正在创建中的Bean的beanName。
  • FactoryBean: 看到这个类,我们很容易想到BeanFactory,其实他两没有关系,IOC中我们已经了解BeanFactory是一个工厂的顶层接口,定义了IOC容器的基本行为,获取bean及bean的各种属性。而FactoryBean是一个工厂Bean接口,目的是用来隐藏复杂Bean的实例化细节,子类通过定制来实现实例化的逻辑,用来产生其他Bean实例。看下它的源码结构

    image-20210708213402792

    其中getObject()方法会返回创建的Bean实例;getObjectType()返回创建的Bean类型;isSingleton()返回创建的Bean实例的作用域是否是单例,如果返回true,该Bean实例就会放到单例的缓存池中。

  • BeanWrapper: 类似BeanDefinition一样,都对Bean的一种封装,但是它对Bean执行了一些操作,例如设置和检索属性。源码中实际使用的是它的实现类BeanWrapperImpl。


把一件事做到极致就是天分!

公众号:风动草