• Spring源码分析(十六)循环依赖3:其他场景下循环依赖分析,以及神器@Lazy懒加载解决循环依赖


    @Async情况下的循环依赖解析

    问题场景演示

    上一节分析过,就算发生循环依赖且提前AOP,最终也可以通过二级缓存把最终的代理对象放入二级缓存:

    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
    在这里插入图片描述

    exposedObject:初始化后得到的对象
    bean:一开始实例化的原始对象

    大部分情况下exposedObject == bean,什么情况下exposedObject != bean呢?

    先看初始化后方法:

    在这里插入图片描述

    aop其实只是其中一个BeanPostProcessor的功能,并且它的实现是:

    • 发生了循环依赖(提前AOP,二级缓存中有值),返回普通对象,此时exposedObject == bean

    • 没有发生循环依赖(没有提前AOP,二级缓存中无值),则进行AOP返回代理对象

      此时二级缓存中无值,且getSingleton方法的入参allowEarlyReference=false,返回是null
      根本不会进入这段逻辑:

      在这里插入图片描述

    除开aop的BeanPostProcessor会处理传进来Bean之外,其他BeanPostProcessor也会处理,其他BeanPostProcessor有可能会直接替换传进来的Bean。

    比如:

    @ComponentScan(value = "com.yth.application")
    @EnableAspectJAutoProxy
    @EnableAsync//开启异步执行功能
    public class AppConfig {}
    
    @Component
    public class AService {
        @Autowired
        private BService bService;
    
        @Async//开启异步
        public void test() {
            System.out.println(bService);
        }
    }
    
    @Component
    public class BService {
        @Autowired
        private AService aService;
    
        public void test() {
            System.out.println(aService);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    启动,抛异常了,就是刚刚讲的这段代码的else:

    在这里插入图片描述

    此时exposedObject != bean,因为初始化后的时候把exposedObject改了

    在这里插入图片描述

    按照之前分析,有循环依赖,所以AOP的实现中返回的还是原始对象
    但是走到AsyncAnnotationBeanPostProcessor,要支持异步就会生成另外一个代理对象(代理对象中,通过单独开一个线程执行相应方法达到异步执行效果)。

    所以导致exposedObject != bean

    本质原因其实是

    1. AsyncAnnotationBeanPostProcessor,没有实现SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference方法,不支持提前获取引用,即不支持处理循环依赖的情况
    2. 普通BeanPostProcessor正常执行了初始化后增强替换了原始对象,与提前初始化getEarlyBeanReference生成的对象发生冲突

    那为什么要抛异常呢?
    之前的案例,发生循环依赖以后:

    在这里插入图片描述

    B注入的A,是二级缓存中的值,getEarlyBeanReference得到的对象,当前场景下就是aop的代理对象(AsyncAnnotationBeanPostProcessor没有提供这个功能

    而A经过初始化后,拿到的是AsyncAnnotationBeanPostProcessor生成的代理对象,如果不做处理,放入单例池的就是这个代理对象

    放入单例池的和赋值给B的(二级缓存中的对象)不一样,所以要抛异常。

    解决方案

    那怎么办呢?

    1. @Lazy懒注入,打破循环依赖

    在这里插入图片描述

    加了@Lazy就不报错了,为啥?

    因为之前出现一系列问题,是因为发了循环依赖,创建A的过程需要B,创建B的过程又需要A

    但是加了@Lazy注解以后,创建A,填充属性B的时候,发现是@Lazy懒注入,则生成一个代理对象直接赋值了(懒注入原理见前面依赖注入章节),不会再去找B、创建B了,A直接能正常走完后续生命周期流程,最终放入单例池。

    而到单例B创建的时候,填充属性A时直接能从单例池拿到A,B也能正常走完后续生命周期流程。

    当A真正用到属性B,执行其方法的时候,才会去找真正的Bean,此时A早已经在单例池中了。

    即使用 @Lazy 直接打破了循环依赖,根本不会发生循环依赖!!!!从根本解决了问题。

    2. 根据顺序判断到底谁发生了循环依赖

    之前给A加了@Async注解,A本身是发生循环依赖的,所以会报错
    而对于B本身来说,其实并没有发生循环依赖,所以@Async放到B上不会报错

    如果把@Async从AService去掉,放到BService上:

    @Component
    public class AService {
        @Autowired
        private BService bService;
        public void test() {
            System.out.println(bService);
        }
    }
    
    @Component
    public class BService {
        @Autowired
        private AService aService;
        @Async
        public void test() {
            System.out.println(aService);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    不会报错为啥?

    主要原因是顺序,AService是先创建的

    先创建A,先给A填充属性B,发现B没有,会去创建B,B再去填充属性A

    当B去填充A的时候,对于A来说,A发生了循环依赖,所以这里A提前AOP拿到代理对象给B注入

    而B本身并没有发生循环依赖,后续会正常的走完流程,在步骤2.5中,先根据aop的到一个代理对象,在根据Async给AOP的代理对象再生成一个代理对象…最终放到单例池中,是OK的

    在这里插入图片描述

    B成为完全Bean给A赋值后,A继续走后续流程,由于A没有被@Async标记,所以初始化后不会生成新的代理对象(不会与AOP提前生成的代理对象冲突),返回的是原始Bean,按照之前分析最终会用二级缓存的值替换放入单例池中,最终A、B放入单例池的,与他们相互引用的都是同一个对象,正常的。

    3. 改造AsyncAnnotationBeanPostProcessor支持提前引用

    其实本质原因是AsyncAnnotationBeanPostProcessor不支持循环依赖的情况,所以直接从根本解决。

    改造AsyncAnnotationBeanPostProcessor,让他实现SmartInstantiationAwareBeanPostProcessor接口,实现getEarlyBeanReference方法,支持提前生成代理对象,并重写postProcessAfterInitialization方法,模仿AOP的实现,没有发生循环依赖,再生成代理对象,否则返回原来的Bean,即原始对象。(注意AsyncAnnotationBeanPostProcessor的顺序应该是放在AOP实现之后的)

    为什么可行?感兴趣参考:参考链接

    原型Bean情况下的循环依赖解析

    A、B都是原型情况,其实解决不了(也不是解决不了,可能没什么意义或者应用场景):

    • 创建A的时候,给A的属性B填充值,B没有要去创建
    • B创建的过程中,给B的属性A填充值,但是A是原型的…拿不到刚刚创建的原型A,又回new一个A…
    • 会一直循环下去
      所以 A -> B -> A1 -> B1 -> A2 -> B2 …

    一个是原型另一个是单例没关系:

    • 原型A -> 单例B -> 原型A1 -> 单例B结束(这种情况不会发生,因为容器启动的时候就会把所有单例先实例化)
    • 单例A -> 原型B -> 单例A结束

    源码分析
    org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

    在这里插入图片描述

    这里会检查多例Bean是不是已经在创建中了,如果是,说明发生了循环依赖,会抛异常
    org.springframework.beans.factory.support.AbstractBeanFactory#isPrototypeCurrentlyInCreation

    protected boolean isPrototypeCurrentlyInCreation(String beanName) {
        //返回指定的原型bean当前是否正在创建中(在当前线程内)。
        //prototypesCurrentlyInCreation是ThreadLocal
        Object curVal = this.prototypesCurrentlyInCreation.get();
        return (curVal != null &&
                (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    使用@Lazy懒注入其实可以解决,启动的时候不会报错,但每次调用代理对象方法的时候,都会重新创建一个Bean,可能没什么意义。

    构造方法导致的循环依赖解析

    @Component
    public class AService {
        public AService(BService bService) {
            this.bService = bService;
        }
    
        private BService bService;
    
        public void test() {
            System.out.println(bService);
        }
    }
    
    @Component
    public class BService {
        public BService(AService aService) {
            this.aService = aService;
        }
    
        private AService aService;
    
        public void test() {
            System.out.println(aService);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    构造方法中A依赖B,B依赖A
    直接报错,因为此时连对象都实例化不了,半成品都生成不了
    实例化A 需要B -> 实例化B 需要A -> …

    怎么解决?用@Lazy!!!打破循环依赖!!

    在这里插入图片描述

    @Transaction情况下的循环依赖解析

    之前@Async给A会报错,换成@Transaction呢?

    @ComponentScan(value = "com.yth.application")
    @EnableAspectJAutoProxy
    @EnableTransactionManagement//开启事务管理
    public class AppConfig {}
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    不会报错,具体不演示了。

    因为@EnableTransactionManagement注解,并没有向容器里添加BeanPostProcessor,而是Advisor,通过AOP实现的事务管理(AOP的通知,AOP原理以后讲)

    在这里插入图片描述

    自己注入自己的情况下的循环依赖解析

    @Component
    public class AService {
        @Autowired
        private AService aService;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    创建A -> 注入A -> 单例池没有 -> 发现循环依赖 -> 二级缓存也没有 -> 利用三级缓存提前获取引用,存入二级缓存,并返回 -> 赋值给属性A -> A继续走后续生命周期流程…一样的

    总结

    至此,循环依赖告一段落,总结一下三级缓存:

    1. singletonObjects:缓存经过了完整生命周期的bean
    2. earlySingletonObjects:缓存未经过完整生命周期的bean,如果某个bean出现了循环依赖,就会提前把这个暂时未经过完整生命周期的bean放入earlySingletonObjects中,这个bean如果要经过AOP,那么就会把代理对象放入earlySingletonObjects中,否则就是把原始对象放入earlySingletonObjects,但是不管怎么样,就是是代理对象,代理对象所代理的原始对象也是没有经过完整生命周期的,所以放入earlySingletonObjects我们就可以统一认为是未经过完整生命周期的bean。
    3. singletonFactories:缓存的是一个ObjectFactory,也就是一个Lambda表达式。在每个Bean的生成过程中,经过实例化得到一个原始对象后,都会提前基于原始对象暴露一个Lambda表达式,并保存到三级缓存中,这个Lambda表达式可能用到,也可能用不到,如果当前Bean没有出现循环依赖,那么这个Lambda表达式没用,当前bean按照自己的生命周期正常执行,执行完后直接把当前bean放入singletonObjects中,如果当前bean在依赖注入时发现出现了循环依赖(当前正在创建的bean被其他bean依赖了),则从三级缓存中拿到Lambda表达式,并执行Lambda表达式得到一个对象,并把得到的对象放入二级缓存(如果当前Bean需要AOP,那么执行lambda表达式,得到就是对应的代理对象,如果无需AOP,则直接得到一个原始对象)。
    4. 其实还要一个缓存,就是earlyProxyReferences,它用来记录某个原始对象是否进行过AOP了。

    反向分析一下singletonFactories

    为什么需要singletonFactories?假设没有singletonFactories,只有earlySingletonObjects,earlySingletonObjects是二级缓存,它内部存储的是未经过完整生命周期的bean对象,Spring原有的流程是出现了循环依赖的情况下:

    1. 先从singletonFactories中拿到lambda表达式,这里肯定是能拿到的,因为每个bean实例化之后依赖注入之前,就会生成一个lambda表达式放入singletonFactories中
    2. 执行lambda表达式,得到结果,将结果放入earlySingletonObjects中

    那如果没有singletonFactories,该如何把原始对象或AOP之后的代理对象放入earlySingletonObjects中呢?何时放入呢?

    首先,将原始对象或AOP之后的代理对象放入earlySingletonObjects中的有两种:

    1. 实例化之后,依赖注入之前:如果是这样,那么对于每个bean而言,都是在依赖注入之前会去进行AOP,这是不符合bean生命周期步骤的设计的。
    2. 真正发现某个bean出现了循环依赖时:按现在Spring源码的流程来说,就是getSingleton(String beanName, boolean allowEarlyReference)中,是在这个方法中判断出来了当前获取的这个bean在创建中,就表示获取的这个bean出现了循环依赖,那在这个方法中该如何拿到原始对象呢?更加重要的是,该如何拿到AOP之后的代理对象呢?难道在这个方法中去循环调用BeanPostProcessor的初始化后的方法吗?不是做不到,不太合适,代码太丑。最关键的是在这个方法中该如何拿到原始对象呢?还是得需要一个Map,预先把这个Bean实例化后的对象存在这个Map中,那这样的话还不如直接用第一种方案,但是第一种又直接打破了Bean生命周期的设计。

    所以,我们可以发现,现在Spring所用的singletonFactories,为了调和不同的情况,在singletonFactories中存的是lambda表达式,这样的话,只有在出现了循环依赖的情况,才会执行lambda表达式,才会进行AOP,也就说只有在出现了循环依赖的情况下才会打破Bean生命周期的设计,如果一个Bean没有出现循环依赖,那么它还是遵守了Bean的生命周期的设计的。

  • 相关阅读:
    【推搜】embedding评估 | faiss的topK向量检索
    MyBatis源码之MyBatis中SQL语句执行过程
    HTML 颜色名:网页设计的调色板
    4进程地址空间
    Java数组的定义与使用
    BeanFactory实现特点
    全网最硬核 JVM TLAB 分析 4. TLAB 基本流程全分析
    使用JPA和Hibernate查询分页
    Unity 游戏开发、03 基础篇 | C#初级编程
    【Wayland】QtWayland框架分析
  • 原文地址:https://blog.csdn.net/weixin_41947378/article/details/127715554