Spring 源码学习 之 bean 的生命周期+循环依赖

说明:

    这部分是照着视频整理的,难免有逻辑的错误,而且比较混乱,建议直接移步到 “总结”。


控制反转:由Spring 管理类与类之间的关系。
依赖注入 :给 bean 【 在Spring容器中经历了完整周期的Java 对象】属性填充的过程。
Spring 提供了很多扩展点。
(from 官网:
    IoC(控制反转)也称为依赖注入(DI)。这是一个过程,在此过程中,对象仅通过构造方法参数,工厂方法的参数或在对象实例从工厂方法构造或返回后设置的属性来定义其依赖关系,即与它们一起使用的其他对象。然后,容器 在创建 bean 时注入那些依赖项。此过程从根本上讲是相反的过程,因此名称为“控制反转”(IoC),bean 本身通过使用类的直接构造或诸如服务定位器模式的机制来控制其依赖项的实例化或位置。)

循环依赖

强依赖:一个对象中包含了另一个对象的引用;弱依赖:一个对象里面调用了另一个对象的数据。
循环依赖:A包含了B,B包含了A。

//Java 形式:强引用+循环依赖
  A a=new A();
       B b=new B();
       a.setB(b);
       b.setA(a);

Spring 里的:

<bean id="a" class="......">
<property name="b" ref="b"></property>
<bean id="b" class="......">
<property name="a" ref="a"></property>


ApplicationContext context=new ApplicationContext("applicationContext.xml");
context.getBean("a");
//不会报错的。

    ✨默认是单例,是支持循环依赖的。✨
    如果是原型模式,是会报错的。

关闭循环:
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();

ac.register(StringConfig.class);
AbstractAutowireCapableBeanFactory beanFactory=(AbstractAutowireCapableBeanFactory)beanfactoy;
beanfactoy.setAllowCircularReference(false);
//真正初始化bean
ac.refresh();

bean 的生命周期?

Class -> beanDefinition -> Object ( Bean )

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);.
Object object=ac.getBean(object.calss);

    首先会实例化 Spring 容器,然后扫描类,接着会解析各种信息,比如各种注解里的信息, @DependOn、SpringConfig 类上提供的 @ComponeScan (“包名”),通过包名获取到类。但是不是立刻 new 的,因为这个对象可能通过 scope 属性或相关注解指定其作用域 是 prototype,或者在 object 类上加了注解 @Lazy (true) ,获取时才会创建对象。
    对于单例情况,会先 new 一个 beanDefinition 接口的实现类,这个实现类有很多属性,用来存储对象的所有信息,比如类名、父类、描述信息 ( @Description )、注入模型( 单例还是原型?)、是否延迟加载、有无 @DependOn 等, 然后将这个对象放到一个 map ( BeanDefinitionMap ) 中,map 是在工厂 (beanfactory )里的,

BeanDefinition bd=this.beanDefinitionMap.get(beanName);

    多次解析就会得到多个对象,Spring 从 map 中遍历到每个对象,如果验证是单例的,就会 new 这个对象,如果未经过扩展,就会把这个单例对象放到单例池中,单例池 singletonobjects <String,object >也是一个 map (是 DefaultSingletonBeanRegistry 里的一个内部类),是缓存 singletonobjects 的,它并不是Spring容器,只是一个单例的缓冲池 。
    如果进行了扩展(通过实现一个接口 BeanFactorPostProcessor ),是在存到 map 中之后(这个 map 是在 beanfactory 中的), new 之前,先去调用扩展方法,然后把 map 传给方法,map 中有 beanDefinition 对象,对象有 beanClass 属性,放置 object 的类信息,这时 Spring 根据 beanClass 去创建对象。
    beanDefinition 接口的实现类有很多,GenericBeanDefinition、Root…
(    如果 @Component 注解中没有指定 bean 名,Spring 会以小驼峰方式命名。)

beanFactory.getBeanDefinition( beanName ) 

    bean 的初始化是在 finishedBeanFactoryInitialization(beanFactory) 这个方法中完成的。对于单例模式非懒加载对象,是用的 preInstantiateSingletons() ,它的方法里是首先通过 beanNames 这个 list 遍历所有的 bean名字,去获取 bean 的 RootBeanDefinition ,接下来就需要做一些验证,然后就到了 doGetBean(),在 doGetBean() 方法中有 getSingleton() 。

doGetBean() 方法

    有个方法✨ transformedBeanName(name)用来验证,返回值是 String 类型的 beanName,除了我们需要的 bean 需要实例化,Spring 内部还有很多 bean 需要实例化, 验证完后就会调用 getSingleton(beanName) 获取 bean。
    先判断这个类是不是在创建的过程中,然后验证一些信息,完后,才会调用 createBean() 方法。

getSingleton

    该方法是 Spring 从单例缓冲池 singletonobjects 中看 bean 有无实例化,(第一回调用这个方法肯定是没有实例化过的),接下来判断 这个类是不是在创建中,(第一回调用这个方法肯定不是),然后会验证(是否有 depenceOn之类的),验证完毕后判断是否是单例,如果是,就会去调用 createBean() 。如果判断 这个类是在创建中,就会去三级缓存中获取。

createBean()

    从这个方法开始创建 bean 对象,通过 beanDefinition 对象获取 bean 的型,接下来 prepareMethodOvverides() ,
    有 9 个地方调用到 后置处理器【 BeanFactorPostProcessor 里的方法】。
    然后会实例化对象 createBeanInstance(beanName,mbd,arg),但不是那个 bean(bean 是要经过完整的 Spring 生命周期的,生命周期应该是 从 beanDefinition 对象 到 bean 对象),这个方法中会第二次调用后置处理器,去推断构造方法,获取到所有的 Constractor ,通过合适的构造方法去反射实例化对象 instantiateBean(beanName,mbd) 其中有用到 clazz.getDeclaredConstructor() 得到构造方法,然后调用 newInstance 完成对象实例化,实例化成功后,就可以在 instanceWrapper 中的 wrapperObjects 属性中就可以看到实例化对象,(也就是把实例放到包装类 BeanWrapper 中返回的),这时没有完成属性注入,也没有进行初始化回调。接下来会判断一下是否允许循环依赖,有个判断条件: 单例 isSingleton &&✨ 允许循环依赖 allowCircularReference 默认是 true✨&&正在被创建 ealrySingleton…,第一项 和 第三项是肯定为 true 的,所以想要关闭循环依赖,可以修改 Spring 源码的话,就修改 SetAllowCircularReference 为 false;否则

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();

ac.register(StringConfig.class);

AbstractAutowireCapableBeanFactory beanFactory=(AbstractAutowireCapableBeanFactory)beanfactoy;
beanfactoy.setAllowCircularReference(false);

//refresh 是真正初始化 bean 的。
ac.refresh();
AbstractAutowireCapableBeanFactory beanFactory=(AbstractAutowireCapableBeanFactory)beanfactoy;
beanfactoy.setAllowCircularReference(false);

    这时有一步:缓存注解信息,解析合并后的bd对象:利用后置处理器去合并 beanDefinition ,拿到所有需要注入的属性,把属性存到一个集合中。
    如果允许循环依赖的话,是会把对象封装成 singletonFactory(之所以是工厂模式,是为了便于有所扩展),然后放到二级缓存中。
    判断条件成立的话,第四次调用后置处理器,会到 addSingletonFactory(),判断是否需要AOP。
    而在这个 addSingletonFactory() 方法中,有一步是要执行 sigletonFactories .put(beanName,sigletonFactory),这个就是二级缓存。
    接下来,会把 bean 赋给 exposeObject,然后交给
💉populateBean(beanName,mbd,instanceWrapper )💉,这时填充属性,也就是 “自动注入” 。在这个填充属性过程中,是遍历了 BeanPostProcessors(),每个后置处理器会对 bean 进行不同的处理,比方说CommonAnnotationBeanPostProcessors 这个是在 处理 @Resource 时使用的,@AutoWired 是用 AutoWiredAnnotationBeanPostProcessor处理的,

循环依赖,比方说对于 a ,是这样的:
    先会.new a ,创建 a 的实例 createBeanInstance,判断循环依赖存在,放到二级缓存中。然后调用 poulateBean 进行属注入,用到了 inject() 方法,会获取属性 b,那就getBean(B) 尝试去容器中看 b 是否经过实例化,假设这时没有实例化 b 呢,那这时从单例缓冲池 singletonObjects 中获取不到,而且不是创建对象,会去级缓存中获取,就会 new 这个 b,就到 b 的生命从 doGetBean()开始… …而到 注入属性 a 的那一步,也要调用 getSingleton() ,就会调用 getBean(a),就要到单例缓冲池中去看 a 是否经过实例化,而这时 getBean(a) 是获取不到的,因为这时这个 a 还不是 bean,只是一个 Java 对象。 先去三级缓存中尝试获取,获取不到,这时判断为 “正在创建对象”,然后会去级缓存 sigletonFactories 中取,会把对象从二级缓存中移除并放到三级缓存earlySingletonObjectes 中,【代码见下:】然后返回的是从二级缓存中获取到的 singletonObject a ,接下来就会调用 set 方法给 b 注入 a(但这时 a 是对象,并不是 bean ,是个半成品)。这一步完后,就会得到一个 b ,再给 a 注入 这个 b 。这时的 a 就是完整的 bean,也就可以填充给 b 做属性。

//三级缓存中保存所有超前曝光的单例,先尝试从三级缓存中获取
singletonObject=this.earlySingletonObjects.get(beanName);

//如果三级缓存获取不到,为 null 
//而 后面的一定是 true(因为这个是内部再调用的 ,肯定传入的是 true 的。)
if(singletonObject==null && allowEarlyReference)
{
//从二级缓存中去获取
ObjectFactory<?> singletonFactory=this.singletonFactories.get(beanName);
if(singletonFactory!=null)
  {
//这时得到的 是 从二级缓存中获取的半成品 a 
//得到的是创建好的实例 a,但还没有填充属性 b。
singletonObject=singletonFactory.getObject();                    
//放到三级缓存
this.earlySingletonObjectes.put(beanName,singletonObject);
//从二级缓存清除
this.singletonFactories.remove(beanName);
   }
}

... ... 

//返回的就是经过 完整周期 的 bean 了。
return singletonObject;

为什么第二次判断的会是 “正在创建对象” 呢?
    因为在 第一次执行创建这个对象 a ,会先去 singletonObjects 中获取,如果这个单例缓冲池中没有。就会判断是否 正在创建, 然后代码就到了 isPrototypeCurrentlyInCreation(String beanName),会执行 getSingleton 方法,并通过 beforeSingletonCreation(String beanName) 把 beanName 放到一个 Set 集合 singletonsCurrentlyIncreation 中 ,表示这个类正在创建,接下来会去调用 createBean 方法。然后就到了 b 的生命周期,就会 getBean()。

是什么时候放进三级缓存的呢?
    从二级缓存中获取到对象,然后就会 remove 掉,放到三级缓存中。

  • 一级缓存:singletonObjects | Map<String,Object>:
        放置已经被实例化好的 bean【已完成生命周期】 单例 bean 才会存在这里,而原型是不需要缓存的,每次调用 getBean() 去创建新的 bean。是ConcurrentHashMap。

    

  • 二级缓存:sigletonFactories | Map<String,ObjectFactory>
  • 获取的是ObjectFactory类型,需要再进一步 get ,才能获取到对象 。 --循环依赖成立,就放到二级缓存。

    二级缓存用于解决循环依赖,提前暴露工厂,工厂是可以产生 bean 的,这个bean是否经过完整的生命周期,取决于 工厂对bean是否加工完成。比如: x 依赖于 y, y 依赖于 x,对x进行 AOP 处理,如果不缓存这个工厂,那么 y 提前注入的会是x的目标对象,而不是代理对象。有二级缓存,才可以判断类要不要被代理。是HashMap。

    

  • 三级缓存:earlySingletonObjectes | Map<String,Object> :
        早期暴露的存放临时对象–防止工厂重复生产。是HashMap。

三个缓存 + beanDefinition + 后置处理器【策略模式,实现了相同的接口BeanPostProcessor,不同后置处理器对方法的实现不同】=Spring容器【Spring 组件的集合】

1.new a 创建实例:createBeanInstance,判断循环依赖存在,放到二级缓存中,
2.poulateBean 注入属性 b ,这时是 target Object 还没有成为代理对象。
注入属性 b 时会 getBean(b),接着就会 new b,注入 a,就会 getBean(a),从二级缓存 (sigletonFactories Map<String,ObjectFactory>) 放到 b 中。(这时 a 还没有经过 4. AOP )这样从二级缓存拿到的是工厂,这样就能获得 AOP 代理的而不是 new 出来的对象。
3.invokeInitMethod 执行初始化回调方法,从源码可以看到,先解析注解(@PostProcessor),再接口,再xml,
4.完成 AOP

在这里插入图片描述

总结

    以上是看着 B 站视频敲的,逻辑很乱,还有错误,以下是捋清楚了的总结:


(默认单例,而且没有启动懒加载:)

一、Bean 的生命周期

1. 实例化 ApplicationContext 对象
2. 扫描类
3. 解析这个类:
    解析各种信息,比如:注解信息(@DependOn、@ComponeScan(“包名”))
4. 实例化 beanDefinition 接口的实现类存储解析出来的信息:
    类名、父类、注入模型(单例?原型)、是否延迟加载 之类的。
5. put beanDefinationMap
    Spring 从这个map 中遍历每个对象。
6. 验证
    返回 String 类型的 beanName
7. 推断构造方法,反射创建对象
    获取搭配所有的 Constactor,通过合适的构造方法,反射的方式,用 newInstance 实例化对象。实例化后,就可以再 instanceWrapper 中的 wrapperObjects 中看到实例化对象,(也就是说 把实例放到包装类 BeanWrapper)
8. 如果允许循环依赖,把对象封装成 SingletonFactory 放到
sigletonFactories | Map<String,ObjectFactory>
9. 注入属性
    通过 populateBean() 填充属性
10. 判断 bean 的类型,回调 Aware 接口
11. 执行初始化回调方法 invokeInitMethod
    三种方式
(1)@PostConstruct注解
(2)实现 InitializingBean 接口
(3)XML 配置

12. 如果需要代理则完成代理 AOP
13. put 到单例池
    singletonObjects | Map<String,Object>中,是ConcurrentHashMap。bean 完成。

    而对于 原型 prototype 和 每次调用 getBean() 去创建新的 bean。

二、 三级缓存:
  • 一级缓存:
    singletonObjects | Map<String,Object>,是 ConcurrentHashMap:
        放置已经被实例化好的 bean【已完成生命周期】 单例 bean 才会存在这里,而原型是不需要缓存的,每次调用 getBean() 去创建新的 bean。是

  • 二级缓存:
    sigletonFactories | Map<String,ObjectFactory>,是 HashMap。
        获取的是ObjectFactory类型,需要再进一步 get ,才能获取到对象 。 --循环依赖成立,就放到二级缓存。
        HashMap 二级缓存用于解决循环依赖,提前暴露工厂,工厂是可以产生 bean 的,这个bean是否经过完整的生命周期,取决于 工厂对bean是否加工完成。比如: x 依赖于 y, y 依赖于 x,对x进行 AOP 处理,如果不缓存这个工厂,那么 y 提前注入的会是x的目标对象,而不是代理对象。有二级缓存,才可以判断类要不要被代理。

  • 三级缓存:
    earlySingletonObjectes | Map<String,Object>,是 HashMap。
        早期暴露的存放临时对象–防止工厂重复生产。

✨三个缓存 + beanDefinition + 后置处理器【策略模式,实现了相同的接口BeanPostProcessor,不同后置处理器对方法的实现不同】=Spring容器【Spring 组件的集合】

三、解决循环依赖:

    先会 new a ,创建 a 的实例 createBeanInstance,
    因为循环依赖存在, 会把对象封装成 SingletonFactory 放到 sigletonFactories 中。然后调用 populateBean 进行属性注入,用到了 inject() 方法,会获取属性 B,那就调用 getBean(B)
    getBean(B) 会先从单例缓冲池 singletonObjects 中获取,获取不到,因为单例缓冲池中放的是 经历完整生命周期的 bean,接下来就会 和 创建 A 的过程一模一样,就都要到走 bean 的生命周期,那么 sigletonFactories 中就会有两个工厂对象 … …
    到了 B 需要注入属性 A 时,会去 getBean(A)方法,这时会调用 getSingleton(A),可以从二级缓存 sigletonFactories 中获取到工厂,通过工厂的 getObject(A),就可以获取到 A,接下来接下来就会调用 set 方法给 B 注入 A。然后 B 就会走剩下的生命周期,直到走完所有的生命周期,然后就可以给 A 注入 B 了,那么 A 就可以完成属性注入,A 继续走生命周期,这样的话 A、B就分别走完了生命周期并完成了属性注入。

  • 为什么 B 注入 A 时,可以从二级缓存中获取到呢?

    在 getBean(A)中,就会先用 getSingleton(A),总是先从一级缓存中获取,获取不到,接下来 判断是否正在创建,如果是的话,就会先从三级缓存中获取对象,这时是拿不到的,接下来会去 二级缓存中获取对象,是可以取到的,这时会把工厂对象从二级缓存中移除,并放入三级缓存。

  • 为什么 B 注入 A 时,调用 getSingleton(A) 会判断成“正在创建”呢?

    因为在 第一次执行创建这个对象 A ,会通过 beforeSingletonCreation(String beanName) 把 beanName 放到一个 Set 集合 singletonsCurrentlyIncreation 中 ,表示这个类正在创建。

  • 为什么要先从三级缓存中取?

    因为三级缓存中放的是对象,如果能取到,就不去二级找了,那为什么一开始要存到二级缓存中呢? 因为如果直接存到三级缓存,只能存一个对象,比方说 AOP 需要加一些代理的逻辑,就应该用工厂对象。

  • 为什么要从二级缓存中 remove?

    因为如果存在比较复杂的循环依赖的话,可以提高性能。比如 x,y,z 循环依赖,那么第一次注入 y 注入 x 时从二级缓存中返回一个 x ,之后移除,再放到三级缓存中,而 z 注入 x 时就不需要再去 二级缓存中获取 x 了,因为首先是从三级缓存中取,而 remove 应该是为了 GC 吧。
    

@Resource 和 @AutoWired

    @Resource 是 CommonAnnotationBeanPostProcessors 这个后置处理器处理的。
    @AutoWired 是 AutoWiredAnnotationBeanPostProcessor处理的。

    它们都可以用来装配 bean,都可以用于字段或 setter 方法。
    @Autowired 默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的 required 属性为 false。
    @Resource 默认按名称装配,当找不到与名称匹配的bean 时才按照类型进行装配。名称可以通过 name 属性指定,如果没有指定 name 属性,当注解写在字段上时,默认取字段名,当注解写在 setter 方法上时,默认取属性名进行装配。
注意:如果name属性一旦指定,就只会按照名称进行装配。

@Autowire和@Qualifier配合使用效果和@Resource一样:

@Autowired(required = false) @Qualifier("example")
private Example example;

@Resource(name = "example")
private Example example;
@Resource【来源自 JDK】装配顺序
  1. 如果同时指定 name 和type,则从容器中查找唯一匹配的 bean 装配,找不到则抛出异常
  2. 如果指定 name 属性,则从容器中查找名称匹配的bean装配,找不到则抛出异常
  3. 如果指定type属性,则从容器中查找类型唯一匹配的bean装配,找不到或者找到多个抛出异常
  4. 如果都不指定,则自动按照byName方式装配,如果没有匹配,则回退一个原始类型进行匹配,如果匹配则自动装配
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值