Spring源码解析(三)- bean的加载

一、bean的加载

bean加载过程大致步骤如下:

1、转换对应beanName。传入的参数bean可能是别名,也可能是FactoryBean,需要进行一系列的解析。别名的取最终的beanName;FactoryBean则是去掉修饰符&。

2、尝试从缓存或者实例工厂中加载单例。单例在Spring同一容器中只会被创建一次。Spring创建bean的原则:不等bean创建完成就会将创建bean的ObjectFactory提前曝光加入到缓存中,一旦下一个bean创建时候需要依赖上一个bean则直接使用ObjectFactory

3、bean的实例化。会存在诸如BeanFactory并不是直接返回实例本身,需要进行实例化后返回。我们需要的是BeanFactory中定义的factory-method方法中的值。

4、原型模式的依赖检查。只有单例模式下才会尝试解决循环依赖,不进行检查会导致循环依赖。

5、检查parentBeanFactory。如果是BeanFactory,缓存中没有数据会转到父类工厂上去加载,然后递归调用getBean方法。

6、将存储XML配置文件的GenericBeanDefinition转换为RootBeanDefinition。从配置文件读取到的信息是存在在GenericBeanDefinition,而后续操作都是针对RootBeanDefinition,故做个转换。

7、寻找依赖。bean的初始化中很可能会用某些属性,而某些属性很可能是动态配置的,且配置成依赖其他的bean,那么就需要先加载依赖的这个bean。

8、针对不同的scope进行bean的创建。在Spring中存在着不同的scope,其中默认singleton,Spring会根据不同的配置进行不同的初始化策略。

9、类型转换。bean已经实例完了,但是需要进行检查需要的类型是否符合bean的实际类型,如果不符合就转换为requiredType所指的类型。

二、FactoryBean的使用

       一般情况下,Spring通过反射机制利用bean的class属性指定实现类来实例化bean。在某些情况下,实例化bean过程比较复杂,按照传统的方式,则需要在中提供大量的配置信息,配置灵活性有限。Spring提供了FactoryBean的工厂类接口,可以通过实现该接口定制实例化bean的逻辑

FactoryBean接口定义了3个方法:

1、T getObject():返回由FactoryBean创建的bean实例,如果isSingleton为true,则将该实例放到单实例缓冲池中。

2、boolean isSingleton():返回FactoryBean穿件bean实例的作用域是singleton还是prototype.

3、Class getObjectType():返回FactoryBean创建bean类型。

三、缓存中获取单例bean

        单例在Spring的同一容器内只会被创建一次,后续再次获取bean直接从单例缓存中获取。

步骤大概是以下几步;

1、检查缓存中是否存在实例。

2、如果不存在实例,则锁定全局变量进行处理。处理过程中如果此bean正在加载则不处理。处理过程为:

       先从singletonObjects里面获取实例,获取不到再从earlySingletonObjects里面获取。如果还是获取不到,就从SingletonFactories里面获取beanName对应的ObjectFactory,再调用ObjectFactorygetObject来创建bean,并放到earlySingletonObjects里面去,并且从SingletonFactories里面删除掉这个ObjectFactory,而对于后续的所有内存操作只是为了循环依赖检查时候使用,也就是在allowEarlyReference为true的情况下使用。

singletonObjects:用于保存beanName和创建bean实例直接的关系。bean name->bean instance

SingletonFactories:用于保存beanName和创建bean的工厂之间的关系.bean name->ObjectFactory

earlySingletonObjects:也是保存beanName和创建bean实例直接的关系,但是不同的是当一个单例bean被放到这里面之后,那么当bean还在创建的时候,就可以通过getBean获取到,其目的是用来检测循环引用。

registeredSingletons:用来保存当前所有已注册的bean.

四、从bean的实例中获取对象

       从bean的实例中获取对象(getBean)方法中有一个重要的方法getObjectForBeanInstance用于检测bean的正确性。无论从缓冲中获取的bean还是根据不同策略加载bean,我们得到bean之后要检测bean是否是工厂类型的bean,如果是需要调用该Bean对应的factoryBean实例中getObject()作为返回值,但是该返回值都只是原始状态,通过该方法返回我们真正需要工厂bean中定义的factory-method方法中返回的Bean。

getObjectForBeanInstance执行的工作:

1、对FactoryBean正确性的验证。

2、对非FactoryBean不做任何处理。

3、对bean进行转换。

4、将从Factory中解析bean的工作委托给getObjectFromFactoryBean。

5、getObjectFromFactoryBean所做的工作是判断bean是否是单例,是的话使用缓冲来提高性能;否则的话调用doGetObjectFromFactoryBean来处理工厂bean的getObject()返回值问题,即调用ObjectFactory的后处理器(注册的BeanPostProcessor的postProcessObjectFromFactoryBean函数)。

        Spring获取bean的规则:尽可能保证所有bean初始化后都调用注册的BeanPostProcessor的postProcessAfterInitialization方法进行处理。

五、获取单例

        单例通常都是从缓冲中获取,如果缓存中不存在已经加载的单例bean,就需要从头开始bean的加载过程,而Spring中使用getSingLeton的重载方法实现bean的加载过程。

        真正的获取单例bean需要先做一些准备工作,其实现逻辑在ObjectFactory类型的实例singletonFactory中实现(下一块讲解),准备内容大致为以下几点:

1、检查缓存是否已经加载过。

2、若没有加载,则记录beanName的正在加载状态。

3、加载单例前记录加载状态。

4、通过调用参数传入的ObjectFactory的个体Object方法实例化bean。(获取单例bean)。

5、加载单例后的处理方法调用。

6、将结果记录到缓存并删除加载bean过程中所记录的各种辅助状态。

7、返回处理结果。

六、准备创建bean

准备创建bean(createBean)完成的具体步骤以及功能:

1、根据设置的class属性或者根据className来解析Class。

2、对override属性进行标记及验证。其目的因为在Spring配置中存在lookup-method和replace-method,被统一存放在BeanDefinition中的methodOverrides属性里面。

3、应用初始化钱的后处理器,解析指定bean是否存在初始化前的短路操作。如果经过前置处理后返回的结果不为空,就直接返回结果。AOP功能就是基于这儿判断。

       实例化前的后处理器主要是将AbsractBeanDefintion转换为BeanWrapper前的处理,给修改BeanDefinition的一个机会,经过这个方法,bean有可能是个代理bean。

       实例化后的处理器主要是为了保证将注册的后处理器的方法应用到该bean,因为如果返回的bean不为空,便不会再次经理普通bean的创建过程。

4、创建bean。

六、循环依赖

       循环依赖就是循环引用,就是两个或两个以上相互之间的持有对方。循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。Spring容器的循环依赖有两种构造器循环依赖和setter循环依赖。

1、构造器循环依赖

       通过构造器注入构成的循环依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常。循环依赖产生的原因是Spring容器将每一个正在创建的bean标识符放在“当前创建bean池”中,创建完毕将从池中移除。如果创建过程中发现自己已经在池中,则抛出BeanCurrentlyInCreationException异常。

2、setter循环依赖

        通过setter注入方式构成的循环依赖。循环依赖产生的原因是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的bean来完成而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean。

3、prototype范围的依赖处理

        对于prototype作用域bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存prototype作用域的bean,因此无法提前暴露一个创建中的bean.

构造⽅法注⼊和setter注⼊之间的区别吗?

1. 在Setter注⼊,可以将依赖项部分注⼊,构造⽅法注⼊不能部分注⼊。因为调⽤构造⽅法如果传⼊所有的参数就会报错。

2. 如果我们为同⼀属性提供Setter和构造⽅法注⼊,Setter注⼊将覆盖构造⽅法注⼊。但是构造⽅法注⼊不能覆盖setter注⼊值。显然,构造⽅法注⼊被称为创建实例的第⼀选项。

3. 使⽤setter注⼊你不能保证所有的依赖都被注⼊。这意味着你可以有⼀个对象依赖没有被注⼊。在另⼀⽅⾯构造⽅法注⼊直到你所有的依赖都注⼊后才开始创建实例。

4. 在构造函数注⼊,如果A和B对象相互依赖:A依赖于B,B也依赖于A,此时在创建对象的A或者B时,Spring抛出ObjectCurrentlyInCreationException。所以Spring可以通过setter注⼊,从⽽解决循环依赖的问题。

题外话:

Spring依赖注入有3种方式:属性注入、setter方法注入、构造方法注入.

1、属性注入。使用 @Autowired 注解注入。另外也有 @Resource 以及 @Inject 等注解。

2、setter方法注入。set 方法注入太过于臃肿,实际上很少使用。

3、构造方法注入。如果类只有一个构造方法,那么 @Autowired 注解可以省略;如果类中有多个构造方法,那么需要添加上 @Autowired 来明确指定到底使用哪个构造方法。

依赖注入- setter注入
依赖注入-构造器注入

       在 Spring3.0 时代,官方还是提倡 set 方法注入的。不过从 Spring4.x 开始,官方就不推荐这种注入方式了,转而推荐构造器注入。通过构造方法注入的方式,能够保证注入的组件不可变,并且能够确保需要的依赖不为空。此外,构造方法注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态

上面这段话主要说了三件事:

1、依赖不可变:这个好理解,通过构造方法注入依赖,在对象创建的时候就要注入依赖,一旦对象创建成功,以后就只能使用注入的依赖而无法修改了,这就是依赖不可变(通过 set 方法注入将来还能通过 set 方法修改)。

2、依赖不为空通过构造方法注入的时候,会自动检查注入的对象是否为空,如果为空,则注入失败;如果不为空,才会注入成功

3、完全初始化:由于获取到了依赖对象(这个依赖对象是初始化之后的),并且调用了要初始化组件的构造方法,因此最终拿到的就是完全初始化的对象了。

       在 Spring3.0 文档中,官方说如果构造方法注入的话,属性太多可能会让代码变得非常臃肿,那么在 4.0 文档中,官方对这个说法也做了一些订正:如果用构造方法注入的时候,参数过多以至于代码过于臃肿,那么此时你需要考虑这个类的设计是否合理,这个类是否参杂了太多的其他无关功能,这个类是否做到了单一职责。

不推荐属性注入?

        属性注入其实有一个显而易见的缺点,那就是对于 IOC 容器以外的环境除了使用反射来提供它需要的依赖之外,无法复用该实现类因为该类没有提供该属性的 set 方法或者相应的构造方法来完成该属性的初始化。换言之,要是使用属性注入,那么你这个类就只能在 IOC 容器中使用,要是想自己 new 一下这个类的对象,那么相关的依赖无法完成注入。

循环依赖主要场景

Spring内部有三级缓存:

1、单例池 singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例

2、提前曝光对象 earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例

3、提前曝光对象工厂 singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。

总结

       Spring在单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过二级缓存和三级缓存来解决的,其中三级缓存是主要功臣。解决的核心原理就是:在对象实例化之后,依赖注入之前,Spring提前暴露的Bean实例的引用在第三级缓存中进行存储。

拓展:为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?

       如果没有 AOP 代理,二级缓存可以解决问题,但是有 AOP 代理的情况下,只用二级缓存就意味着所有 Bean 在实例化后就要完成 AOP 代理,这样违背了 Spring 设计的原则,Spring 在设计之初就是通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来在 Bean 生命周期的最后一步来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理。

出现循环依赖如何解决?

1、生成代理对象产生的循环依赖

      1)使用@Lazy注解,延迟加载

      2)使用@DependsOn注解,指定加载先后关系

      3)修改文件名称,改变循环依赖类的加载顺序

2、使用@DependsOn产生的循环依赖

这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题。

3、多例循环依赖

这类循环依赖问题可以通过把bean改成单例的解决。

4、构造器循环依赖

这类循环依赖问题可以通过使用@Lazy注解解决。

七、创建bean

         当经历过resolveBeforeInstantiation方法后,程序有两个选择,如果创建了代理或者说重写了InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法并在方法postProcessBeforeInstantiation中改变了bean,则直接返回就可以了,否则需要进行常规bean的创建。而常规bean的创建就是在doCreateBean中完成的。

常规bean的创建大致步骤:

(1)如果是单例则需要首先清除缓存。

(2)实例化bean,将BeanDefinition转换为BeanWrapper。转换的大致功能:

        1)如果存在工厂方法则使用工厂方法进行初始化

        2)一个类有多个构造函数,每个构造函数都有不同的参数,所以需要根据参数锁定构造函数并进行初始化

        3)如果既不存在工厂方法也不存在带有参数的构造函数,则使用默认的构造函数进行bean的实例化

(3)MergedBeanDefinitionPostProcessor的应用。bean合并后的处理,Autowired注解正是通过此方法实现诸如类型的预解析。

(4)依赖处理。只处理单例的循环处理,处理流程是通过放入缓存中的ObjectFactory来创建实例,来解决循环依赖的。

(5)属性填充。将所有属性填充至bean的实例中,其中可能存在依赖其他bean属性,则会递归初始依赖bean。

(6)循环依赖检查。检测已经加载的bean是否已经出现了依赖循环,并判断抛出异常。是通过判断是否存在依赖,获取所有依赖bean的名称,递归检测依赖bean是否已经创建,如果没有创建完,也就是说存在循环依赖。

(7)注册DisposableBean。如果配置了destory-method,需要注册以便于在销毁时候调用。

(8)完成创建并返回

7.1 创建bean的实例

创建bean的实例大致过程为:

(1) 首先先判断工厂方法是否为空,如果不为空就使用工厂方法初始化策略。也就是说如果在RootBeanDefinition中存在factoryMethodName属性,或者说在配置文件中配置了factory-method,那么Spring会尝试使用instantiateUsingFactoryMethod(beanName,mbd,args)方法根据RootBeanDefinition中配置生成bean的实例。

(2) 解析构造函数并进行构造函数的实例化。因为一个bean对应的类中可能会有多个构造函数,而每个构造函数的参数不同,Spring在根据参数及类型去判断最终会使用哪个构造函数进行实例化。判断过程比较消耗性能,所以采用了缓存机制,如果已经解析过则使用解析好的构造函数方法不需要再次锁定,否则需要再次解析,并将结果添加到RootBeanDefinition中的属性resolvedConstructorOrFactoryMethod方法中。然后根据对应的参数解析构造函数,无参的按照默认构造函数构造进行实例化。

7.1.1 autowireConstructor

      上面提到对于实例的创建Spring中分成了两种情况,一种是通用的实例化,另一种是带有参数的实例化。带有参数的实例化过程相当复杂,因为存在着不确定性,所以在判断对应参数上做了大量工作。

autowireConstructor实现的功能考虑了一些几个方面:

(1)构造函数参数的确定

    1)根据explicitArgs参数判断。如果传入的参数explicitArgs不为空,那么就可以直接确定参数,因为explicitArgs参数是在调用bean的时候用户指定的。在获取bean的时候,用户不但可以指定bean的名称还可以指定Bean多对应类的构造函数或者工厂方法的方法参数,主要用于静态工厂方法调用。

    2)缓存中获取。如果确定参数的方法之前已经分析过,那么会记录到缓存中,但是缓存中存储的可能是参数的最终类型也可能是参数的初始化类型,需要经过类型转化器的过滤。

    3)配置文件获取。如果通过上面的方式都不能获取到,可以从配置文件中进行获取。另外配置文件信息都会经过BeanDefinition实例承载,获取参数信息包括直接指定值等。

(2)构造函数的确定。是根据构造函数参数在构造函数中锁定对应的函数。匹配规则是:对构造函数按照public构造函数优先参数数量降序,非public构造函数参数数量降序。另外配置文件还支持通过设置指定参数名称来确定,获取参数名称有两种方式,一种通过注解方式,另外一种使用Spring中提供的工具类来获取。构造函数、参数名称、参数类型、参数值都确定了就可以锁定构造函数以及转换对应的参数类型。

(3)根据确定的构造函数转换对应的参数类型。主要使用Spring中提供的类型转换器或者用户提供的自定义转换器进行转换。

(4)构造函数不确定性的验证。有时候参数锁定之后也不能完全锁定构造函数,不同构造函数的参数为父子关系,所以在最后做一次验证。

(5)根据实例化策略以及得到的构造函数及构造函数参数实例化Bean。

7.1.2 instantiateBean

       无参实例构造直接调用实例化策略进行实例化就可以了。

7.1.3实例化策略

       经过前面的分析,可以得到足够实例化的信息,但是Spring并没有直接利用反射方法进行实例化,而是判断beanDefinition.getMethodOverrides()是否为空,也就是用户没有使用replace或者lookup的配置方法。但是如果使用了着两个特性,所以就必须要使用cglib动态代理的方式将包含两个特性所对应的逻辑的拦截增强器设置进去,这样才可以保证在调用方法发时候会被相应的拦截器增强,返回值为包含拦截器的代理实例。

7.2 记录创建bean的ObjectFactory

       ObjectFactory的实现主要用于处理循环依赖。在使用之前会进行判断是否是单例、是否允许循环、是否对应的bean正在创建中,才会执行加入单例工厂。

7.3 属性注入

        关于循环依赖一直涉及populateBean函数,这个函数主要是用于属性填充。

在populateBean函数中提供了这样的处理流程。

(1)InstantiationAwareBeanPostProcessor处理器的postProcessAfterInstantiation函数的应用,此函数可以控制程序是否继续进行属性填充。

(2)根据注入类型(byName,ByType)提取依赖的bean,并统一存入PropertyValues中。

(3)应用InstantiationAwareBeanPostProcessor处理器的postProcessPropertyValues方法,对属性获取完毕填充前对属性的再次处理,典型应用是RequiredAnnotationBeanPostProcessor类中对属性的验证。

(4)将所有propertyValues中的属性填充至BeanWrapper中

Spring基于注解的属性注入方法:

1.@Autowried

1)默认基于类型查找容器的的Bean进行注入(注入的Bean的实现类是唯一的)。

2)当实现类的Bean大于一个的时候,需结合@Qualifier进行指定,根据Bean的名称来指定需要注入的实例。或者使用@Primary注解标注具体的Bean来让Spring优先选择该Bean进行注入

3)@Autowried注解可以标注在构造方法、set方法(属性)、字段

      @Qualifier标注在方法参数、set方法、字段上、类型上

      @Primary标注在类上以及方法上

2.@Resource(JSR250的标准)

1)默认是基于Bean的名称进行注入,如果查找的name不存在,则会根据类型进行查找

2)当指定name属性时,则只按照名称进行查找

3)当指定type属性时,则只按照类型进行查找

4)当指定name和type时,则这两个属性必须同时满足才可注入

7.3.1 autowireByName

        根据名称自动匹配的第一步就是寻找bw中需要依赖注入的属性,,然后遍历这些属性并寻找名称匹配的bean(在传入的参数pvs中找出已经加载的bean),并递归实例化,进而加入到pvs中

7.3.2 autowireByType

        对于根据类型自动匹配的实现来讲第一步也是寻找bw中需要依赖注入的属性,然后遍历这些属性并寻找类型匹配的bean,其中最复杂的就是寻找类型匹配的bean。同时,Spring提供对集合类型的注入支持,正因为这一因素,所以在autowireByType函数中,新建了局部遍历autowireBeanNames,用于存储所有依赖的bean,对于非集合类的属性注入,此属性无用。

     根据类型进行注入有3种情况:1、ObjectFactory特殊类型;2、javaxInjectProviderClass类注入;3、通用处理逻辑。

       寻找类型的匹配执行顺序时,首先尝试使用解析器进行解析如果解析器没有成功解析,则使用了自定义的解析器。但是对于集合等类型来说并不在解析范围之内,所以再次对不同类型进行不同情况的处理,虽说对于不同类型处理方式不一致,但是大致的思路还是很相似的。

7.3.3 applyPropertyValues

applyPropertyValues主要用于获取属性。

7.4 初始化bean

       bean配置中有个init-method属性,这个属性的作用就是在bean实例化前调用init-method指定方法来根据用户业务进行相应的实例化

       Spring中程序已经执行过bean的实例化,并且进行了属性的填充,而就在这时将会调用用户设定的初始化方法。虽说主要目的是进行客户设定的初始化方法的调用,但是除此之外还有些其他必要的工作。

(1)激活Aware,用于注入Aware相关一些实例。

       Spring中提供一些Aware相关接口,比如BeanFactoryAware、ApplicationContextAware、ResourceLoaderAware、ServletContextAware等,实现这些Aware接口的bean在被初始之后,可以取得一些相应的资源,例如实现BeanFactoryAware的bean在初始后,Spring容器将会注入BeanFactory的实例,而实现ApplicationContextAware的bean,在bean被初始后,将会被注入ApplicationContext的实例等。

(2)处理器的应用

      BeanPostProcessor是Spring有提供给用户权限去更改、扩展Spring在bean初始化之前调用客户自定义初始化方法之前以及调用自定义初始化方法后分别会调用BeanPostProcessor的postProcessBeforeInitialization和postProcessAfterInitialization方法,使用户可以根据自己的业务需求进行响应的处理。

(3)激活自定义的init方法

         客户定制的初始化方法除了我们熟知的使用配置init-method外,还有使自定义的bean实现InitializingBean接口,并在afterPropertiesSet中实现自己的初始化业务逻辑。init-methodafterPropertiesSet都是在初始化bean时执行,执行顺序是afterPropertiesSet先执行,而init-method后执行。

        在invokeInitMethods方法中就实现了这两个步骤的初始化方法调用。属性初始化后处理、自定义初始化方法。

7.5 注册DisposableBean

        Spring中不但提供了对初始化方法的扩展入口,同样也提供了销毁方法的扩展入口,对于销毁方法的扩展,除了我们熟知的配置属性destroy-method方法外,用户还可以注册后处理器DestructionAwareBeanPostProcessor来统一处理bean的销毁方法。

你可能感兴趣的:(Spring源码解析(三)- bean的加载)