Spring框架解决循环依赖的方案——三级缓存机制


      最近在复习Spring框架相关基础知识,面试题中经常出现的Spring框架解决循环依赖问题也看了很多网上的博客,以下是作为本人学习记录,如有不合理的地方,欢迎指正!

问题引入:

       什么是循环依赖?循环依赖是指在对象创建过程中,对象的属性、或者构造器参数、或者方法参数依赖其他对象:比如A对象的setter方法的入参(也可以是构造器入参,也可以是接口方法入参)是对象B,而B对象中同样有A对象作为setter方法的入参,两者相互引用构成引用的闭环,这个例子就是最简单的循环依赖的案例;

代码如图:

@NoArgsConstructor
public class StudentA {

    private StudentB  studentB;


    public StudentA(StudentB studentB){
        this.studentB = studentB;

    }

    public void setStudentB(StudentB studentB) {
        this.studentB = studentB;
    }
}
@NoArgsConstructor
public class StudentB {

    private StudentA studentA;

    public StudentB(StudentA studentA){
        this.studentA = studentA;
    }

    public void setStudentA(StudentA studentA) {
        this.studentA = studentA;
    }
}

 

  <bean id="a" name="a" class="com.test.cycleDependency.StudentA" scope="prototype">
        <property name="studentB" ref="b"/>
    </bean>

    <bean id="b" name="b" class="com.test.cycleDependency.StudentB" scope="prototype">
        <property name="studentA" ref="a"/>
    </bean>
public class TestCycle {
    public static void main(String[] args) {

        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("applicationContext.xml");

        StudentA a = (StudentA)context.getBean("a");

        System.out.println(a);
    }
}

      上面给出的案例是依赖setter注入方式的依赖(而且依赖是protype多例模式):上述代码执行main方法后会提示Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

就是出现循环依赖的报错。原因就在于如果是protype的方式setter,依赖每次创建的是一个新的bean,而不会放入一级缓存中,也就避开了spring的三级缓存机制,所以报错;

那如果去掉scope="prototype"属性改为默认单例就可以解决报错了;同样如果使用<constructor-arg/>构造器方式注入依赖,也会报循环依赖无法解决的错误,原因就在于循环依赖解决的是bean对象已经实例化(理解为已经创建了对象的引用),但是还没有属性赋值的阶段对象的依赖问题,对于还没有进行实例化的bean依赖它也是无法处理的,而我们通过构造器注入的bean恰恰就是在实例化的时候需要引入的,互相依赖的双方都还没有实例化,自然无法拿到对象的引用啦!

总结:

     三级缓存可以解决setter注入单例模式下的循环依赖;但是对于以下方式产生的循环依赖也是无法解决的:

    1、通过构造器参数注入的方式;

     2、setter方法参数注入但是多例对象的时候(scope="prototype");

 

     Spring三级缓存是什么机制:在Spring中有三级缓存机制,一级缓存(singletonObjects)、二级缓存(earlySingletonObjects)、三级缓存(singletonFactories);

源码如下:

  private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);//一级缓存:主要存放的是已经实例化并且属性已经赋值的bean对象
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);//三级缓存:主要存放已经beanName和beanFactory(bean实例工厂)的对应关系
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);//二级缓存:主要存放已经实例化,但是对象属性还未赋值的bean对象

//doGetBean操作的核心源码
@Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//首先通过beanName去一级缓存查找
        Object singletonObject = this.singletonObjects.get(beanName);
        if(singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
//假如一级缓存不存在实例,进一步去二级缓存查找,如果此时找到直接进行返回
            singletonObject = this.earlySingletonObjects.get(beanName);
//如果未找到二级缓存未找到实例对象,检查allowEarlyReference是否为true,这个标志就是二级缓存解决循环依赖的关键,假如将这个标志设为false,
//那么spring解决循环依赖问题也就失效了(默认为true);这个标志叫做是否允许循环引用,也就是二级缓存中存放的仅仅是对象实例的早期引用,对象的属性还未做赋值
            if(singletonObject == null && allowEarlyReference) {
                Map var4 = this.singletonObjects;
                synchronized(this.singletonObjects) {

                    singletonObject = this.singletonObjects.get(beanName);
                    if(singletonObject == null) {
//这里二级缓存的查找实际查找的是对象实例的引用
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if(singletonObject == null) {
//重复一二级缓存查找的过程,如果还是没有则通过三级缓存创建beanName对应的singletonFactory(单例工厂)
                            ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                            if(singletonFactory != null) {
//由单例工厂创建一个bean的实例
重点讲下这个getObjeact方法:
singletonObject = singletonFactory.getObject(); //将该实例放入二级缓存(注:此时只是做了bean的实例化,未对属性赋值) this.earlySingletonObjects.put(beanName, singletonObject); //从三级缓存中移除该对象的单例工厂 this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }

      

紧接着我们还得继续分析创建bean的过程:bean的创建经历四步(主要中间do开头的两步)getBean——》doGetBean——》createBean——》doCreateBean;

如果上面的getSingleton()方法没有拿到bean怎么办?也就是下面来到docreateBean的代码:

 

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
        throws BeanCreationException {

    // ①:创建bean实例,通过反射实例化bean,就是创建代理对象
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);

    // bean = 通过代理对象创建的实例
    Object bean = instanceWrapper.getWrappedInstance();

    // ②:是否需要将早期的bean暴露出去,所谓早期的bean相当于这个bean就是通过new的方式创建了这个对象,但是这个对象还没有填充属性,所以是个半成品
    // 是否需要将早期的bean暴露出去,判断规则(bean是单例 && 是否允许循环依赖 && bean是否在正在创建的列表中)
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));

    if (earlySingletonExposure) {
        //③:调用addSingletonFactory方法,这个方法内部会将其丢到第3级缓存中,getEarlyBeanReference的源码大家可以看一下,内部会调用一些方法获取早期的bean对象,比如可以在这个里面通过aop生成代理对象
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // 这个变量用来存储最终返回的bean
    Object exposedObject = bean;
    //填充属性,这里面会调用setter方法或者通过反射将依赖的bean注入进去
    populateBean(beanName, mbd, instanceWrapper);
    //④:初始化bean,内部会调用BeanPostProcessor的一些方法,对bean进行处理,这里可以对bean进行包装,比如生成代理
    exposedObject = initializeBean(beanName, exposedObject, mbd);


    //早期的bean是否被暴露出去了
    if (earlySingletonExposure) {
        /**
         *⑤:getSingleton(beanName, false),注意第二个参数是false,这个为false的时候,
         * 只会从第1和第2级中获取bean,此时第1级中肯定是没有的(只有bean创建完毕之后才会放入1级缓存)
         */
        Object earlySingletonReference = getSingleton(beanName, false);
        /**
         * ⑥:如果earlySingletonReference不为空,说明第2级缓存有这个bean,二级缓存中有这个bean,说明了什么?
         * 大家回头再去看看上面的分析,看一下什么时候bean会被放入2级缓存?
         * (若 bean存在三级缓存中 && beanName在当前创建列表的时候,此时其他地方调用了getSingleton(beanName, false)方法,那么bean会从三级缓存移到二级缓存)
         */
        if (earlySingletonReference != null) {
            //⑥:exposedObject==bean,说明bean创建好了之后,后期没有被修改
            if (exposedObject == bean) {
                //earlySingletonReference是从二级缓存中获取的,二级缓存中的bean来源于三级缓存,三级缓存中可能对bean进行了包装,比如生成了代理对象
                //那么这个地方就需要将 earlySingletonReference 作为最终的bean
                exposedObject = earlySingletonReference;
            } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                //回头看看上面的代码,刚开始exposedObject=bean,
                // 此时能走到这里,说明exposedObject和bean不一样了,他们不一样了说明了什么?
                // 说明initializeBean内部对bean进行了修改
                // allowRawInjectionDespiteWrapping(默认是false):是否允许早期暴露出去的bean(earlySingletonReference)和最终的bean不一致
                // hasDependentBean(beanName):表示有其他bean以利于beanName
                // getDependentBeans(beanName):获取有哪些bean依赖beanName
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    //判断dependentBean是否已经被标记为创建了,就是判断dependentBean是否已经被创建了
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                /**
                 *
                 * 能走到这里,说明早期的bean被别人使用了,而后面程序又将exposedObject做了修改
                 * 也就是说早期创建的bean是A,这个A已经被有些地方使用了,但是A通过initializeBean之后可能变成了B,比如B是A的一个代理对象
                 * 这个时候就坑了,别人已经用到的A和最终容器中创建完成的A不是同一个A对象了,那么使用过程中就可能存在问题了
                 * 比如后期对A做了增强(Aop),而早期别人用到的A并没有被增强
                 */
                if (!actualDependentBeans.isEmpty()) {
                    //弹出异常(早期给别人的bean和最终容器创建的bean不一致了,弹出异常)
                    throw new BeanCurrentlyInCreationException(beanName,"异常内容见源码。。。。。");
                }
            }
        }
    }

    return exposedObject;
}

请注意阅读以下bean在三级缓存中的流转过程:

6.3、下面来看 A、B 类 setter 循环依赖的创建过程

1、getSingleton("a", true) 获取 a:会依次从 3 个级别的缓存中找 a,此时 3 个级别的缓存中都没有 a

2、将 a 丢到正在创建的 beanName 列表中(Set<String> singletonsCurrentlyInCreation)

3、实例化 a:A a = new A();这个时候 a 对象是早期的 a,属于半成品

4、将早期的 a 丢到三级缓存中(Map<String, ObjectFactory<?> > singletonFactories)

5、调用 populateBean 方法,注入依赖的对象,发现 setB 需要注入 b

6、调用 getSingleton("b", true) 获取 b:会依次从 3 个级别的缓存中找 a,此时 3 个级别的缓存中都没有 b

7、将 b 丢到正在创建的 beanName 列表中

8、实例化 b:B b = new B();这个时候 b 对象是早期的 b,属于半成品

9、将早期的 b 丢到三级缓存中(Map<String, ObjectFactory<?> > singletonFactories)

10、调用 populateBean 方法,注入依赖的对象,发现 setA 需要注入 a

11、调用 getSingleton("a", true) 获取 a:此时 a 会从第 3 级缓存中被移到第 2 级缓存,然后将其返回给 b 使用,此时 a 是个半成品(属性还未填充完毕)

12、b 通过 setA 将 11 中获取的 a 注入到 b 中

13、b 被创建完毕,此时 b 会从第 3 级缓存中被移除,然后被丢到 1 级缓存

14、b 返回给 a,然后 b 被通过 A 类中的 setB 注入给 a

15、a 的 populateBean 执行完毕,即:完成属性填充,到此时 a 已经注入到 b 中了

16、调用a= initializeBean("a", a, mbd)对 a 进行处理,这个内部可能对 a 进行改变,有可能导致 a 和原始的 a 不是同一个对象了

17、调用getSingleton("a", false)获取 a,注意这个时候第二个参数是 false,这个参数为 false 的时候,只会从前 2 级缓存中尝试获取 a,而 a 在步骤 11 中已经被丢到了第 2 级缓存中,所以此时这个可以获取到 a,这个 a 已经被注入给 b 了

18、此时判断注入给 b 的 a 和通过initializeBean方法产生的 a 是否是同一个 a,不是同一个,则弹出异常

  • 二级缓存是用于存放半成品的Bean,存放在二级缓存中的是注入对象的代理,每次获取创建中的对象先从二级缓存中查询是因为这样就可以拿到代理对象,而不用每次都包装代理返回提高执行性能
  • 三级缓存是用于存放原始的注入对象,这些对象还是简单对象并没有被代理,三级缓存是用于解决循环依赖问题,当对象A依赖对象B时,A对象就会被先临时存放在三级缓存中,然后初始化B对象。在这个时间点如果有别的注入对象需要依赖A对象就会从三级缓存中查询,并通过三级缓存的 singletonFactory.getObject()方法生成代理对象然后将A对象从三级缓存中删除,放入二级缓存(存放注入对象的代理)。
     

针对二级缓存中的wrap包装代理如下步骤,我们知道面向切面编程实现的原理就是动态代理,我们来看下getEarlyBeanReference方法的源码

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
        implements AutowireCapableBeanFactory {
        
    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        // 1.如果bean不为空 && mbd不是合成 && 存在InstantiationAwareBeanPostProcessors
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                // 2.应用所有SmartInstantiationAwareBeanPostProcessor,调用getEarlyBeanReference方法
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    // 3.允许SmartInstantiationAwareBeanPostProcessor返回指定bean的早期引用
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        // 4.返回要作为bean引用公开的对象,如果没有SmartInstantiationAwareBeanPostProcessor修改,则返回的是入参的bean对象本身
        return exposedObject;
    }
}

在开起AOP的情况下,

那么就是调用到AnnotationAwareAspectJAutoProxyCreator的父类的AbstractAutoProxyCreator的getEarlyBeanReference方法,对应的源码如下:
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
        
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        // 如果需要代理,返回一个代理对象,不需要代理,直接返回当前传入的这个bean对象
        return wrapIfNecessary(bean, beanName, cacheKey);
    }
}

上面

wrapIfNecessary为Spring实现Bean代理的核心方法

  • wrapIfNecessary在两处会被调用,一处是getEarlyBeanReference,另一处是postProcessAfterInitialization
  • 在wrapIfNecessary方法内部调用getAdvicesAndAdvisorsForBean()返回匹配当前Bean的所有Advice\Advisor\Interceptor,用于判断此该类是否需要创建代理。
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
        
    /**
     * Spring实现Bean代理的核心方法。wrapIfNecessary在两处会被调用,一处是getEarlyBeanReference,
     * 另一处是postProcessAfterInitialization
     */
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        //已经被处理过
        // 1.判断当前bean是否在targetSourcedBeans缓存中存在(已经处理过),如果存在,则直接返回当前bean
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        //不需要被织入逻辑的
        // 2.在advisedBeans缓存中存在,并且value为false,则代表无需处理
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
        //是不是基础的bean 是不是需要跳过的
        // 3.bean的类是aop基础设施类 || bean应该跳过,则标记为无需处理,并返回
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }

        // Create proxy if we have advice.
        // 返回匹配当前Bean的所有Advice\Advisor\Interceptor
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        // 5.如果存在增强器则创建代理
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            //创建Bean对应的代理,SingletonTargetSource用于封装实现类的信息
            // 5.1 创建代理对象:这边SingletonTargetSource的target属性存放的就是我们原来的bean实例(也就是被代理对象),
            // 用于最后增加逻辑执行完毕后,通过反射执行我们真正的方法时使用(method.invoke(bean, args))
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            // 5.2 创建完代理后,将cacheKey -> 代理类的class放到缓存
            this.proxyTypes.put(cacheKey, proxy.getClass());
            // 返回代理对象
            return proxy;
        }
        //该Bean是不需要进行代理的,下次就不需要重复生成了
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
}
 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM