简谈spring循环依赖

103 阅读2分钟

参阅5.3源码

最近这段时间,各种喜羊羊,美羊羊出现,也确实许久未外出跑动了。就算外出也只是买菜,拿下快递。就算外出,也不敢跑,也怕羊羊,于是家里蹲。还望大家注意防护,增强抵抗力。废话有点多,文章如下:

根据Bean的生命周期

Bean生成有几个重要步骤 
1.实例化得到对象 
(扫描ClassPathBeanDefinitionScanner#doScan) 
2.填充属性(AbstractAutowireCapableBeanFactory#populateBean) 
3.初始化前(BeanPostProcessor#postProcessBeforeInitialization) 
4.初始化 
5.初始化后 
(BeanPostProcessor#postProcessAfterInitialization) 
6.放入单例池(singletonObjects)

简单描述过程

  1. 实例化TeacherService->放入三级缓存singletonFactories(原生对象或代理对象)
  2. 填充StudentService属性->从单例池->没有需要创建
  3. 实例化StudentService->放入三级缓存
  4. 填充TeacherService->单例池->TeacherService创建中出现循环->earlySingletonObjects->执行singletonFactories的Object,放入二级缓存earlySingletonObjects且移除三级缓存。

循环依赖

就是循环引用即两个或两个以上的Bean相互引用. 
以及时序图:

sequenceDiagram
TeacherService->>StudentService: 上课啦
StudentService->>TeacherService: 听讲

源码关键点

  1. 添加三级缓存 ;有三个前提条件必须是单例,支持循环依赖且正在创建(在集合中@(singletonsCurrentlyInCreation) 
    故而:原型,构造方法是不能解决循环依赖。
//判断单例,支持循环依赖且正在创建(Set<String> )
// TODO 循环依赖-添加到三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  1. 根据Bean生命周期,AOP是在初始化后调用 
    出现循环依赖,会在初始化对象的时候,放入singletonFatory->lamdba getEarlyBeanReference,该方法中提前进行AOP(AbstractAutoProxyCreator)操作。
    //TODO 提前代理引用
    private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        return wrapIfNecessary(bean, beanName, cacheKey);
    }

解决循环依赖的三个缓存

主要源码(DefaultSingletonBeanRegistry)下

    //TODO 一级缓存,完整生命周期的Bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    //TODO 三级缓存,主要打破循环依赖的关键
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    //TODO 保证AOP或原始对象是单例(早期的Bean对象,未经过完整生命周期)
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

开始打破关键代码块

选取AbstractAutowireCapableBeanFactory#doCreateBean关键部分

// TODO 为了解决循环依赖提前缓存单例创建工厂
// TODO 判断单例,支持循坏引用且当前正在创建
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            // TODO 循环依赖-添加到三级缓存
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }
        //目的很清楚,主要就是为了往singletonFatories存放对象,lamdba表达式在初始化之后运行,获取原生对象或代理对象
/**
     * TODO 关键代码,注释也使相当清楚
     * Add the given singleton factory for building the specified singleton
     * if necessary.
     * <p>To be called for eager registration of singletons, e.g. to be able to
     * resolve circular references.
     * @param beanName         the name of the bean
     * @param singletonFactory the factory for the singleton object
     */
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

注意:循环依赖,使用AOP

如果在当前方法中增加Async注解方法,则会报错。

image.png

image.png 后置方法中执行AOP的逻辑,返回的是Bean而非代理对象;因为cacheKey在循环依赖,就在earlyProxyReference集合中存在

image.png

异常原因就在于bean在Bean初始化后置方法中(AOP)被修改了;则exposedObject!=bean导致错误。

解决方法

TeacherService @Lazy注解的方式,在Bean生命周期过程中提前进行AOP返回代理对象。 还有一种方案,就是根据Service创建过程根据排序规则(ASCII排序),StudentService会先执行。TeacherService后执行,将方法移入TeacherService中也可以解决。