7. IOC初始化流程
IoC容器的初始化就是含有BeanDefinition信息的Resource的定位、载入、解析、注册四个过程,最终我们配置的bean,以beanDefinition的数据结构存在于IoC容器即内存中。这里并不涉及bean的依赖注入,只是bean定义的载入。但有例外,在使用Ioc容器时有一个预实例化的配置,即bean定义中的设置了lazyinit属性,那么这个bean在Ioc容器初始化时就预先加载,不需要等到Ioc整个初始化后,第一次getBean时才会触发。其中refresh()启动对Ioc容器的初始化。
主要分为如下三个步骤:
- Resource定位过程。
这个Resource定位指的是BeanDefinition的资源定位,它由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用提供了统一接口。对于这些BeanDefinition的存在形式,相信大家都不会感到陌生。比如说,在文件系统中的Bean定义信息可以使用FileSystemResource来进行抽象;在类路径中可以使用前面提到的ClassPathResource来使用,等等。这个过程类似于容器寻找数据的过程,就像用水桶装水先要把水找到一样。
- BeanDefinition的载入
该载入过程把用户定义好的Bean表示成IoC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition,下面可以看到这个数据结构的详细定义。总地说来,这个BeanDefinition实际上就是POJO对象在IoC容器中的抽象,这个BeanDefinition定义了一系列的数据来使得IoC容器能够方便地对POJO对象也就是Spring的Bean进行管理。即BeanDefinition就是Spring的领域对象。
- 向IoC容器注册这些BeanDefinition的过程
这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的,这个注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册。可以看到,在IoC容器内部将BeanDefinition注入到一个HashMap中去,Ioc容器是通过这个HashMap来持有这些BeanDefinition数据的。整个过程可以理解为容器的初始化过程。
容器的初始化是通过AbstractApplicationContext的refresh()实现的,下面将会对这个函数进行详细介绍。
7.1 refresh函数
Spring中会经常使用到AnnotationConfigApplicationContext
作为IOC容器的操作入口,可以利用该context进行Bean的管理,从下面的构造函数可以看到,refresh函数中完成了IOC容器的初始化,因此弄清楚refresh函数就理解了IOC的初始化流程。
下面对每个函数仔细分析。
1. prepareRefresh() 预处理
- initPropertySources()初始化一些属性设置;子类自定义个性化的属性设置方法
- getEnvironment().validateRequiredProperties();校验属性的合法等
- earlyApplicationEvents = new LinkedHashSet
() 保存容器中一些早期的事件
2. obtainFreshBeanFactory() 获取BeanFactory
这一步重点是refreshBeanFactory(),
- refreshBeanFactory()
刷新【创建】容器,创建了一个this.beanFactory = new DefaultListableBeanFactory();并设置序列化id。 - getBeanFactory();
返回上一步创建的beanFactory对象 - 将创建BeanFactory【DefaultListableBeanFactory】返回
3. prepareBeanFactory(beanFactory) BeanFactory的预准备工作
- 设置beanFactory的类加载器、支持表达式解析器
- 添加部分ApplicationContextAwareProcessor
- 设置忽略的自动装配的接口EnvironmentAware、EmbeddedValueResolverAware等等
- 注册可以解析的自动装配;我们能直接在任何组件中自动注入
- BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext
- 添加BeanPostProcessor【ApplicationListenerDetector】
- 添加编译时的AspectJ
- 给BeanFactory中注册一些能用的组件 environment【ConfigurableEnvironment】、systemProperties【Map<String, Object>】、systemEnvironment【Map<String, Object>】
4. postProcessBeanFactory(beanFactory) beanFactory准备工作完成后进行的后置处理
子类通过重写这个方法来在beanFactory创建并预准备完成以后做进一步的设置,以上4步是beanFactory的创建以及预准备工作。
5. invokeBeanFactoryPostProcessors(beanFactory) 调用beanFactory后置处理器
这部分主要执行实现了如下两个接口的类方法:BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor。
1. 先执行BeanDefinitionRegistryPostProcessor的方法
1)获取所有的BeanDefinitionRegistryPostProcessor
2)先执行实现了PriorityOrdered接口的获取所有的BeanDefinitionRegistryPostProcessor
1 | postProcessor.postProcessBeanDefinitionRegistry(registry) |
3)在执行实现了Ordered顺序接口的BeanDefinitionRegistryPostProcessor
4)最后执行没有任何优先级或者是顺序接口的BeanDefinitionRegistryPostProcessor
2. 在执行BeanFactoryPostProcessor的方法
1)获取所有的BeanFactoryPostProcessor
2)看先执行实现了PriorityOrdered接口的获取所有的BeanFactoryPostProcessor
3)在执行实现了Ordered顺序接口的BeanFactoryPostProcessor
4)最后执行没有任何优先级或者是顺序接口的BeanFactoryPostProcessor
6. registerBeanPostProcessors(beanFactory);注册BeanPostProcessor(bean的后置处理器)
将不同类型的BeanPostProcessor加入到BeanFactory中,注意到这里是依据优先级依次注册。
1)获取所有的BeanPostProcessor;后置处理器都默认可以通过PriorityOrdered、Ordered接口执行优先级。
2)先注册PriorityOrdered优先级的BeanPostProcessor;把每一个BeanPostProcessor添加到BeanFactory中。
3)接着注册Ordered接口的。
4)然后注册没有任何优先级接口的。
5)注册一个ApplicationListenerDetector;来在bean创建完成后检查是否是ApplicationListener如果是监听器。
这个步骤中的所有后置处理器,都是通过下面的getBean方法来进行实例化的,具体流程在之前AOP中有介绍。实例化之后,在后续注册Bean的时候,就可以对Bean的生成进行定制化。
7. initMessageSource() 初始化messageSource组件
1)获取BeanFactory
2)看容器中是否有id为messageSource的,类型是MessageSource的组件
如果有复制给messageSource,如果没有创建一个DelegatingMessageSource
3)把创建好的MessageSource注册到容器中,以后获取国际化配置文件的值的时候可以自动注入MessageSource
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
8. initApplicationEventMulticaster()
1)获取BeanFactory
2)从BeanFactory中获取ApplicationEventMulticaster
3)如果没有上一步配置,那就创建一个SimpleApplicationEventMulticaster
4)将创建的ApplicationEventMulticaster添加到BeanFactory中,以后其他组件直接自动注入
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
9. onRefresh()
空函数,子类可以重写这个方法,在容器刷新的时候可以自定义逻辑(比如增加组件)
10. registerListeners()
1)从容器中拿到所有的ApplicationListener。
2)将每个监听器添加到时间派发器中
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
3)派发之前步骤产生的事件
11. finishBeanFactoryInitialization(beanFactory);初始化所有剩下的单实例bean
这是所有步骤中,最重要,最复杂的一步,之前AOP中对这部分有过仔细介绍,这里重点梳理正常Bean的初始化流程。
12. finishRefresh() 完成BeanFactory的初始化创建工作,IOC容器就创建完成
1.initLifecycleProcessor()初始化和生命周期有关的后置处理器;
LifecycleProcessor默认从容器中找是否有LifecycleProcessor的组件
2.getLifecycleProcessor().onRefresh();
拿到前面定义的生命周期处理器(BeanFactory) 回调onRefresh
3.publishEvent(new ContextRefreshedEvent(this));发布容器刷新完成事件
4.LiveBeansView.registerApplicationContext(this);
7.2 Spring-bean的循环依赖以及解决方式
什么是循环依赖?
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:
@w=250
注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。
Spring中循环依赖场景有:
(1)构造器的循环依赖
(2)field属性或者setter的循环依赖。
下面的例子中, 会发生这种循环依赖的情况:
1 | @Component |
怎么检测是否存在循环依赖?
检测循环依赖相对比较容易,Bean在创建的时候可以给该Bean加标记,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。
Spring怎么解决field属性和setter的循环依赖?
Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或则属性是可以延后设置的(但是构造器必须是在获取引用之前)。
Spring的单例对象的初始化主要分为三步:
(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
(3)initializeBean:调用spring xml中的init 方法。
从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二部。也就是构造器循环依赖和field循环依赖。
那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。
三级缓存主要指:
1 | /** Cache of singleton objects: bean name --> bean instance */ |
这三级缓存分别指:
singletonFactories : 单例对象工厂的cache
earlySingletonObjects :提前曝光的单例对象的Cache
singletonObjects:单例对象的cache
我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就就是:
1 | protected Object getSingleton(String beanName, boolean allowEarlyReference) { |
上面的代码需要解释两个参数:
- isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
- allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:
1 | this.earlySingletonObjects.put(beanName, singletonObject); |
从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。
从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义如下:
1 | public interface ObjectFactory<T> { |
这个接口在下面被引用
1 | protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { |
这里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
下面是整段代码:
这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。
总结来讲,Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
如何解决构造器中的循环依赖?
1. 使用@Lazy注解
最简单的方法是使用@Lazy声明其中的一个Bean,这样的话Spring将会创建代理对象,并注入到其他依赖于它的Bean中,这个注入的Bean将会在第一次被使用的时候初始化。
1 | @Component |
2. 替换构造器依赖,改为setter/Field注入
上面提到了Spring可以解决setter/Field中的循环依赖,因此可以将构造器中的依赖Bean,改为在setter/Field中进行注入,例子如下:
1 | @Component |
3. 实现ApplicationContextAware和InitializingBean
让其中的一个类实现ApplicationContextAware和InitializingBean,来手动设置依赖的Bean。
ApplicationContextAware
发生在调用初始化方法之前,也就是下面的第二步,因此可以获取到ApplicationContext。
实现InitializingBean
需要重写其的afterPropertiesSet
方法,这发生在第3步,此时类中已经有了ApplicationContext,所以直接拿到对应的Bean实例即可。
因为这两个方法都发生在createBeanInstance之后,所以在缓存singletonFactories中拿到对应的Bean。
具体例子如下:
1 | @Component |
参考:
- https://blog.csdn.net/u010853261/article/details/77940767
- https://www.baeldung.com/circular-dependencies-in-spring
7.3 spring依赖注入注解的实现原理
7.3.1 @Autowired的工作原理
以下是@Autowired注解的源码,从源码中看到它可以被标注在构造函数、属性、setter方法或配置方法上,用于实现依赖自动注入。
1 |
|
@Autowired注解的作用是由AutowiredAnnotationBeanPostProcessor
实现的,查看该类的源码会发现它实现了MergedBeanDefinitionPostProcessor接口,进而实现了接口中的postProcessMergedBeanDefinition方法,@Autowired注解正是通过这个方法实现注入类型的预解析,将需要依赖注入的属性信息封装到InjectionMetadata类中,InjectionMetadata类中包含了哪些需要注入的元素及元素要注入到哪个目标类中。
1 | public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter |
@Autowired
发生在refresh方法的finishBeanFactoryInitialization
(beanFactory)阶段,在此之前,在registerBeanPostProcessors(beanFactory)已经完成了对AutowiredAnnotationBeanPostProcessor的注册。
在doCreateBean方法,首先会调用applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName),实质上就是调用AutowiredAnnotationBeanPostProcessor类的postProcessMergedBeanDefinition方法,也就是开头介绍的在这个方法中完成了对注入元素注解的预解析。接着,在doCreateBean方法中执行populateBean方法实现对属性的注入。
深入分析populateBean方法,下面是关键部分,这段代码中会遍历所有注册过的BeanPostProcessor接口实现类的实例,如果实例属于InstantiationAwareBeanPostProcessor类型的,则执行实例类的postProcessPropertyValues方法。
从下面的类继承关系可以看到,这里会执行AutowiredAnnotationBeanPostProcessor类的postProcessPropertyValues方法,
具体代码如下:
metadata.inject(bean, beanName, pvs)代码的执行会进入如下inject方法中,在这里完成依赖的注入。
上面的InjectedElement有两个子类,分别是AutowiredFieldElement和AutowiredMethodElement,AutowiredFieldElement用于对标注在属性上的注入,AutowiredMethodElement用于对标注在方法上的注入。
两种方式的注入过程都差不多,根据需要注入的元素的描述信息,按类型或名称查找需要的依赖值,如果依赖没有实例化先实例化依赖,然后使用反射进行赋值。
7.3.2 @Resource和@Inject的工作原理
两者的实现原理与@Autowired类似,不过这两者是JDK中提供的annotation,是通过BeanPostProcessor接口的实现类CommonAnnotationBeanPostProcessor来实现的,其中如名字所述,即公共注解CommonAnotation,CommonAnnotationBeanPostProcessor是spring中统一处理JDK中定义的注解的一个BeanPostProcessor。该类会处理的注解还包括@PostConstruct,@PreDestroy等。
7.3.3 注解处理器的激活条件
AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor添加到spring容器的BeanPostProcessor的条件,即激活这些处理器的条件如下:
- 基于xml的spring配置
在对应的spring容器的配置xml文件中,如applicationContext.xml,添加<context:annotation-config />和<context:component-scan />,或者只使用<context:component-scan />。
两者的区别是<context:annotation-config />只查找并激活已经存在的bean,如通过xml文件的bean标签生成加载到spring容器的,而不会去扫描如@Controller等注解的bean,查找到之后进行注入;而<context:component-scan />除了具有<context:annotation-config />的功能之外,还会去加载通过basePackages属性指定的包下面的,默认为扫描@Controller,@Service,@Component,@Repository注解的类。不指定basePackages则是类路径下面,或者如果使用注解@ComponentScan方式,则是当前类所在包及其子包下面。
- 基于配置类的spring配置
如果是基于配置类而不是基于applicationContext.xml来对spring进行配置,如SpringBoot,则在内部使用的IOC容器实现为AnnotationConfigApplicationContext或者其派生类,在AnnotationConfigApplicationContext内部会自动创建和激活以上的BeanPostProcessor。
如果同时存在基于xml的配置和配置类的配置,而在注入时间方面,基于注解的注入先于基于XML的注入,所以基于XML的注入会覆盖基于注解的注入。
7.3.4 总结
- @Autowired是Spring自带的,@Inject和@Resource都是JDK提供的,其中@Inject是JSR330规范实现的,@Resource是JSR250规范实现的,而Spring通过BeanPostProcessor来提供对JDK规范的支持。
- @Autowired、@Inject用法基本一样,不同之处为@Autowired有一个required属性,表示该注入是否是必须的,即如果为必须的,则如果找不到对应的bean,就无法注入,无法创建当前bean。
- @Autowired、@Inject是默认按照类型匹配的,@Resource是按照名称匹配的。如在spring-boot-data项目中自动生成的redisTemplate的bean,是需要通过byName来注入的。如果需要注入该默认的,则需要使用@Resource来注入,而不是@Autowired。
- 对于@Autowire和@Inject,如果同一类型存在多个bean实例,则需要指定注入的beanName。@Autowired和@Qualifier一起使用,@Inject和@Name一起使用。
参考:
本文由『后端精进之路』原创,首发于博客 http://teckee.github.io/ , 转载请注明出处
搜索『后端精进之路』关注公众号,立刻获取最新文章和价值2000元的BATJ精品面试课程。