Java充电社
专辑
博文
联系我
本人继续续收门徒,亲手指导
Spring教程
-> 面试官:循环依赖不用三级缓存可以么?
1、Spring是什么?我们为什么要学习它?
2、控制反转(IoC)与依赖注入(DI)
3、Spring容器基本使用及原理
4、xml中bean定义详解
5、容器创建bean实例有多少种?
6、bean作用域scope详解
7、依赖注入之手动注入
8、依赖注入之自动注入(autowire)详解
9、depend-on干什么的?
10、primary可以解决什么问题?
11、bean中的autowire-candidate属性又是干什么的?
12、lazy-init:bean延迟初始化
13、使用继承简化bean配置(abstract & parent)
14、lookup-method和replaced-method比较陌生,怎么玩的?
15、代理详解(java动态代理&CGLIB代理)
16、深入理解java注解(预备知识)
17、@Configration、@Bean注解详解
18、@ComponentScan、@ComponentScans详解
19、@Import 注解详解
20、@Conditional通过条件来控制bean的注册
21、注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
22、@Scope、@DependsOn、@ImportResource、@Lazy
23、Bean生命周期详解
24、父子容器
25、@PropertySource、@Value注解及动态刷新实现
26、国际化详解
27、事件详解
28、循环bean详解
29、BeanFactory扩展(BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor)
30、jdk动态代理和cglib代理
31、Aop概念详解
32、AOP核心源码、原理详解
33、ProxyFactoryBean创建AOP代理
34、@Aspect中@Pointcut 12种用法
35、@Aspect中5中通知详解
36、@EnableAspectJAutoProxy、@Aspect中通知顺序详解
37、@EnableAsync & @Async 实现方法异步调用
38、@Scheduled & @EnableScheduling定时器详解
39、强大的Spel表达式
40、缓存使用(@EnableCaching、@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig)
41、@EnableCaching集成redis缓存
42、JdbcTemplate实现增删改查如此简单?
43、Spring中编程式事务怎么用的?
44、Spring声明事务怎么用的?
45、详解Spring事务中7种传播行为
46、Spring如何管理多数据源事务?
47、Spring事务源码解析
48、@Transaction 事务源码解析
49、实战篇:手把手带你实现事务消息!
50、Spring事务拦截器顺序如何控制?
51、Spring事务失效常见的几种情况
52、Spring实现数据库读写分离
53、Spring集成MyBatis
54、集成junit
55、Spring上下文生命周期
56、面试官:循环依赖不用三级缓存可以么?
57、Spring常用工具类
上一篇:Spring上下文生命周期
下一篇:Spring常用工具类
<div style="display:none"></div> 今天来聊一个面试中经常会被问到的问题,咱们一起必须把这个问题搞懂。 问题:**spring中为什么需要用三级缓存来解决这个问题?用二级缓存可以么?** 我先给出答案:**不可用**。 这里先声明下: **本文未指明bean scope默认情况下,所有bean都是单例的,即scope是singleton,即下面所有问题都是在单例的情况下分析的。** **代码中注释很详细,一定要注意多看代码中的注释。** ## 1、循环依赖相关问题 1、什么是循环依赖? 2、循环依赖的注入对象的2种方式:构造器的方式、setter的方式 3、构造器的方式详解 4、spring是如何知道有循环依赖的? 5、setter方式详解 6、需注意循环依赖注入的是半成品 7、为什么必须用三级缓存? ## 2、什么是循环依赖? A依赖于B,B依赖于A,比如下面代码 ```java public class A { private B b; } public class B { private A a; } ``` ## 3、循环依赖注入对象的2种方式 ### 3.1、构造器的方式 通过构造器相互注入对方,代码如下 ```java public class A { private B b; public A(B b) { this.b = b; } } public class B { private A a; public B(A a) { this.a = a; } } ``` ### 3.2、setter的方式 通过setter方法注入对方,代码如下 ```java public class A { private B b; public B getB() { return b; } public void setB(B b) { this.b = b; } } public class B { private A a; public A getA() { return a; } public void setA(A a) { this.a = a; } } ``` ## 4、构造器的方式详解 ### 4.1、构造器的方式知识点 1、构造器的方式如何注入? 2、循环依赖,构造器的方式,spring的处理过程是什么样的? 3、循环依赖构造器的方式案例代码解析 ### 4.2、构造器的方式如何注入? 再来看一下下面这2个类,相互依赖,通过构造器的方式相互注入对方。 ```java public class A { private B b; public A(B b) { this.b = b; } } public class B { private A a; public B(A a) { this.a = a; } } ``` 大家来思考一个问题:**2个类都只能创建一个对象,大家试试着用硬编码的方式看看可以创建这2个类的对象么?** 我想大家一眼就看出来了,无法创建。 创建A的时候需要先有B,而创建B的时候需要先有A,导致无法创建成功。 ### 4.3、循环依赖,构造器的方式,spring的处理过程是什么样的? spring在创建bean之前,会将当前正在创建的bean名称放在一个列表中,这个列表我们就叫做singletonsCurrentlyInCreation,用来记录正在创建中的bean名称列表,创建完毕之后,会将其从singletonsCurrentlyInCreation列表中移除,并且会将创建好的bean放到另外一个单例列表中,这个列表叫做singletonObjects,下面看一下这两个集合的代码,如下: ```java 代码位于org.springframework.beans.factory.support.DefaultSingletonBeanRegistry类中 //用来存放正在创建中的bean名称列表 private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); //用来存放已经创建好的单例bean,key为bean名称,value为bean的实例 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); ``` 下面我们来看下面2个bean的创建过程 ```java @Compontent public class A { private B b; public A(B b) { this.b = b; } } @Compontent public class B { private A a; public B(A a) { this.a = a; } } ``` 过程如下 ```java 1、从singletonObjects查看是否有a,此时没有 2、准备创建a 3、判断a是否在singletonsCurrentlyInCreation列表,此时明显不在,则将a加入singletonsCurrentlyInCreation列表 4、调用a的构造器A(B b)创建A 5、spring发现A的构造器需要用到b 6、则向spring容器查找b,从singletonObjects查看是否有b,此时没有 7、spring准备创建b 8、判断b是否在singletonsCurrentlyInCreation列表,此时明显不在,则将b加入singletonsCurrentlyInCreation列表 9、调用b的构造器B(A a)创建b 10、spring发现B的构造器需要用到a,则向spring容器查找a 11、则向spring容器查找a,从singletonObjects查看是否有a,此时没有 12、准备创建a 13、判断a是否在singletonsCurrentlyInCreation列表,上面第3步中a被放到了这个列表,此时a在这个列表中,走到这里了,说明a已经存在创建列表中了,此时程序又来创建a,说明这么一直走下去会死循环,此时spring会弹出异常,终止bean的创建操作。 ``` ### 4.4、通过这个过程,我们得到了2个结论 1、循环依赖如果是构造器的方式,bean无法创建成功,这个前提是bean都是单例的,bean如果是多例的,大家自己可以分析分析。 2、**spring是通过singletonsCurrentlyInCreation这个列表来发现循环依赖的,这个列表会记录创建中的bean,当发现bean在这个列表中存在了,说明有循环依赖,并且这个循环依赖是无法继续走下去的,如果继续走下去,会进入死循环,此时spring会抛出异常让系统终止。** 判断循环依赖的源码在下面这个位置,`singletonsCurrentlyInCreation`是Set类型的,Set的add方法返回false,说明被add的元素在Set中已经存在了,然后会抛出循环依赖的异常。 ```java org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); protected void beforeSingletonCreation(String beanName) { //bean名称已经存在创建列表中,则抛出循环依赖异常 if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { //抛出循环依赖异常 throw new BeanCurrentlyInCreationException(beanName); } } //循环依赖异常 public BeanCurrentlyInCreationException(String beanName) { super(beanName, "Requested bean is currently in creation: Is there an unresolvable circular reference?"); } ``` ### 4.5、spring构造器循环依赖案例 创建类A ```java package com.javacode2018.cycledependency.demo1; import org.springframework.stereotype.Component; @Component public class A { private B b; public A(B b) { this.b = b; } } ``` 创建类B ```java package com.javacode2018.cycledependency.demo1; import org.springframework.stereotype.Component; @Component public class B { private A a; public B(A a) { this.a = a; } } ``` 启动类 ```java package com.javacode2018.cycledependency.demo1; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan public class MainConfig { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(MainConfig.class); //刷新容器上下文,触发单例bean创建 context.refresh(); //关闭上下文 context.close(); } } ``` 运行上面的main方法,产生了异常,部分异常信息如下,说明创建bean`a`的时候出现了循环依赖,导致创建bean无法继续进行,以后大家遇到这个错误了,应该可以很快定位到问题了。 ```java Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference? at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:347) ``` ## 5、setter方式详解 再来看看setter的2个类的源码 ```java public class A { private B b; public B getB() { return b; } public void setB(B b) { this.b = b; } } public class B { private A a; public A getA() { return a; } public void setA(A a) { this.a = a; } } ``` 大家试试通过硬编码的方式来相互注入,很简单吧,如下面这样 ``` A a = new A(); B b = new B(); a.setB(b); b.setA(a); ``` 咱们通过硬编码的方式可以搞成功的,spring肯定也可以搞成功,确实,setter循环依赖,spring可以正常执行。 下面来看spring中setter循环依赖注入的流程。 ## 6、spring中setter循环依赖注入流程 spring在创建单例bean的过程中,会用到三级缓存,所以需要先了解三级缓存。 ### 6.1、三级缓存是哪三级? spring中使用了3个map来作为三级缓存,每一级对应一个map | 第几级缓存 | 对应的map | 说明 | | ---------- | ------------------------------------------------ | ------------------------------------------------------------ | | 第1级 | Map<String, Object> singletonObjects | 用来存放已经完全创建好的单例bean<br /><br />beanName->bean实例 | | 第2级 | Map<String, Object> earlySingletonObjects | 用来存放早期的bean<br /><br />beanName->bean实例 | | 第3级 | Map<String, ObjectFactory<?>> singletonFactories | 用来存放单例bean的ObjectFactory<br /><br />beanName->ObjectFactory实例 | 这3个map的源码位于`org.springframework.beans.factory.support.DefaultSingletonBeanRegistry`类中。 ### 6.2、单例bean创建过程源码解析 代码入口 ```java org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean ``` #### step1:doGetBean 如下,这个方法首先会调用getSingleton获取bean,如果可以获取到,就会直接返回,否则会执行创建bean的流程 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/138/19629e72-d34c-4338-8041-66ac2b44dfc5.png) #### step2:getSingleton(beanName, true) 源码如下,这个方法内部会调用`getSingleton(beanName, true)`获取bean,注意第二个参数是`true`,这个表示是否可以获取早期的bean,这个参数为true,会尝试从三级缓存`singletonFactories`中获取bean,然后将三级缓存中获取到的bean丢到二级缓存中。 ```java public Object getSingleton(String beanName) { return getSingleton(beanName, true); } protected Object getSingleton(String beanName, boolean allowEarlyReference) { //从第1级缓存中获取bean Object singletonObject = this.singletonObjects.get(beanName); //第1级中没有,且当前beanName在创建列表中 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { //从第2级缓存汇总获取bean singletonObject = this.earlySingletonObjects.get(beanName); //第2级缓存中没有 && allowEarlyReference为true,也就是说2级缓存中没有找到bean且beanName在当前创建列表中的时候,才会继续想下走。 if (singletonObject == null && allowEarlyReference) { //从第3级缓存中获取bean ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); //第3级中有获取到了 if (singletonFactory != null) { //3级缓存汇总放的是ObjectFactory,所以会调用其getObject方法获取bean singletonObject = singletonFactory.getObject(); //将3级缓存中的bean丢到第2级中 this.earlySingletonObjects.put(beanName, singletonObject); //将bean从三级缓存中干掉 this.singletonFactories.remove(beanName); } } } } return singletonObject; } ``` #### step3:getSingleton(String beanName, ObjectFactory<?> singletonFactory) 上面调用`getSingleton(beanName, true)`没有获取到bean,所以会继续走bean的创建逻辑,会走到下面代码,如下 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/138/35575235-8040-49d2-a086-8114b546618f.png) 进入`getSingleton(String beanName, ObjectFactory<?> singletonFactory)`,源码如下,只留了重要的部分 ```java public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { //从第1级缓存中获取bean,如果可以获取到,则自己返回 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { //将beanName加入当前创建列表中 beforeSingletonCreation(beanName); //①:创建单例bean singletonObject = singletonFactory.getObject(); //将beanName从当前创建列表中移除 afterSingletonCreation(beanName); //将创建好的单例bean放到1级缓存中,并将其从2、3级缓存中移除 addSingleton(beanName, singletonObject); } return singletonObject; } ``` 注意代码`①`,会调用`singletonFactory.getObject()`创建单例bean,我们回头看看`singletonFactory`这个变量的内容,如下图,可以看出主要就是调用`createBean`这个方法 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/138/a9298d62-04d3-4161-b0d9-d04496c2c85c.png) 下面我们进入`createBean`方法,这个内部最终会调用`doCreateBean`来创建bean,所以我们主要看`doCreateBean`。 #### step4:doCreateBean ```java protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // ①:创建bean实例,通过反射实例化bean,相当于new X()创建bean的实例 BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); // bean = 获取刚刚new出来的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; } ``` 上面的step1~step4,大家要反复看几遍**,下面这几个问题搞清楚之后,才可以继续向下看,不懂的结合源码继续看上面几个步骤** **1、什么时候bean被放入3级缓存?** 早期的bean被放入3级缓存 **2、什么时候bean会被放入2级缓存?** 当beanX还在创建的过程中,此时被加入当前beanName 创建列表了,但是这个时候bean并没有被创建完毕(bean被丢到一级缓存才算创建完毕),此时bean还是个半成品,这个时候其他bean需要用到beanX,此时会从三级缓存中获取到beanX,beanX会从三级缓存中丢到2级缓存中。 **3、什么时候bean会被放入1级缓存?** bean实例化完毕,初始化完毕,属性注入完毕,bean完全组装完毕之后,才会被丢到1级缓存。 **4、populateBean方法是干什么的?** 填充属性的,比如注入依赖的对象。 ### 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进入到2级缓存的时候,说明这个bean的早期对象被其他bean注入了,也就是说,这个bean还是半成品,还未完全创建好的时候,已经被别人拿去使用了,所以必须要有3级缓存,2级缓存中存放的是早期的被别人使用的对象,如果没有2级缓存,是无法判断这个对象在创建的过程中,是否被别人拿去使用了。** 3级缓存是为了解决一个非常重要的问题:早期被别人拿去使用的bean和最终成型的bean是否是一个bean,如果不是同一个,则会产生异常,所以以后面试的时候被问到为什么需要用到3级缓存的时候,你只需要这么回答就可以了:**三级缓存是为了判断循环依赖的时候,早期暴露出去已经被别人使用的bean和最终的bean是否是同一个bean,如果不是同一个则弹出异常,如果早期的对象没有被其他bean使用,而后期被修改了,不会产生异常,如果没有三级缓存,是无法判断是否有循环依赖,且早期的bean被循环依赖中的bean使用了。**。 spring容器默认是不允许早期暴露给别人的bean和最终的bean不一致的,但是这个配置可以修改,而修改之后存在很大的分享,所以不要去改,通过下面这个变量控制 ```java org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#allowRawInjectionDespiteWrapping private boolean allowRawInjectionDespiteWrapping = false; ``` ### 6.4、模拟BeanCurrentlyInCreationException异常 **来个登录接口ILogin** ```java package com.javacode2018.cycledependency.demo2; //登录接口 public interface ILogin { } ``` 来2个实现类 **LoginA** 这个上面加上`@Component`注解,且内部需要注入`X` ```java package com.javacode2018.cycledependency.demo2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class LoginA implements ILogin { @Autowired private X x; public X getX() { return x; } public void setX(X x) { this.x = x; } } ``` **LoginC**,不需要spring来管理 ```java package com.javacode2018.cycledependency.demo2; //代理 public class LoginC implements ILogin { private ILogin target; public LoginC(ILogin target) { this.target = target; } } ``` **X类**,有@Component,且需要注入Ilogin对象,这个地方会注入LoginA,此时LoginA和X会参数循环依赖 ```java package com.javacode2018.cycledependency.demo2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class X { @Autowired private ILogin login; public ILogin getLogin() { return login; } public void setLogin(ILogin login) { this.login = login; } } ``` 添加一个BeanPostProcessor类,实现`postProcessAfterInitialization`方法,这个方法发现bean是loginA的时候,将其包装为LoginC返回,这个方法会在bean创建的过程中调用`initializeBean`时候被调用 ```java package com.javacode2018.cycledependency.demo2; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; @Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (beanName.equals("loginA")) { //loginA实现了ILogin return new LoginC((ILogin) bean); } else { return bean; } } } ``` **spring配置类** ```java package com.javacode2018.cycledependency.demo2; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan public class MainConfig { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(MainConfig.class); context.refresh(); context.close(); } } ``` 运行输出,产生了`BeanCurrentlyInCreationException`异常,是因为注入给x的是LoginA这个类的对象,而最后容器中beanname:loginA对应的是LoginC了,导致注入给别人的对象和最中的对象不一致了,产生了异常。 ``` Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'loginA': Bean with name 'loginA' has been injected into other beans [x] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example. ``` ## 7、案例:若只使用2级缓存会产生什么后果? 下面来个案例,通过在源码中设置断点的方式,来模拟二级缓存产生的后果。 ### 添加A类 我们希望loginA在A类之前被创建好,所以这里用到了@DependsOn注解。 ```java package com.javacode2018.cycledependency.demo3; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component; @Component @DependsOn("loginA") //类A依赖于loginA,但是又不想通过属性注入的方式强依赖 public class A { } ``` ### 接口ILogin ```java package com.javacode2018.cycledependency.demo3; //登录接口 public interface ILogin { } ``` 来2个实现类,LoginA需要spring管理,LoginC不需要spring管理。 ### LoginA ```java package com.javacode2018.cycledependency.demo3; import org.springframework.stereotype.Component; @Component public class LoginA implements ILogin { } ``` ### LoginC ```java package com.javacode2018.cycledependency.demo3; //代理 public class LoginC implements ILogin { private ILogin target; public LoginC(ILogin target) { this.target = target; } } ``` ### MyBeanPostProcessor 负责将loginA包装为LoginC ```java package com.javacode2018.cycledependency.demo3; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; @Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (beanName.equals("loginA")) { //loginA实现了ILogin return new LoginC((ILogin) bean); } else { return bean; } } } ``` ### 启动类MainConfig ```java package com.javacode2018.cycledependency.demo3; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan public class MainConfig { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(MainConfig.class); context.refresh(); context.close(); } } ``` ### 下面模拟只使用二级缓存的情况 在bean被放到三级缓存之后,下面的一行代码处设置断点,操作如下 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/138/82c9349c-1fea-4e15-869c-ba5c13244a11.png) 会弹出一个框,然后填入下面配置,这个配置表示满足条件的时候,这个断点才会起效 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/138/1939ba34-f215-495d-8b0b-c5c557dcf43f.png) ### debug方式运行程序 走到了这个断点的位置,此时loginA已经被放到第3级缓存中,此时如果我们调用`this.getSingleton(beanName,true)`,loginA会从第3级缓存移到第3级,这个时候就相当于只有2级缓存了,操作如下 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/138/3cc88acb-c6a6-4be8-b6e7-e28d615e91bf.png) 点击下面的按钮,会弹出一个窗口,可以在窗口中执行代码,执行`this.getSingleton(beanName,true)`,即将`loginA`从三级缓存放到2级缓存,这样相当于没有3级缓存了。 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/138/76781037-18e3-40a9-a44f-773801c27d28.png) ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/138/4f6b05bb-58b4-49fe-8f89-9becb56dda4e.png) 运行结果,最终也参数了`BeanCurrentlyInCreationException`异常,实际上这个程序并没有出现循环依赖的情况,但是如果只用了二级缓存,也出现了早期被暴露的bean和最终的bean不一致的问题所参数的异常。 ```java Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException ``` 这个程序如果不进行干预,直接运行,是可以正常运行的,只有在3级缓存的情况才可以正常运行。 ## 8、总结 今天的内容有点多,大家慢慢消化,有问题欢迎留言! ## 9、案例源码 ```java git地址: https://gitee.com/javacode2018/spring-series 本文案例对应源码: spring-series\lesson-008-junit ``` **本博客所有系列案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。** <a style="display:none" target="_blank" href="https://mp.weixin.qq.com/s/_S1DD2JADnXvpexxaBwLLg" style="color:red; font-size:20px; font-weight:bold">继续收门徒,亲手带,月薪 4W 以下的可以来找我</a> ## 最新资料 1. <a href="https://mp.weixin.qq.com/s?__biz=MzkzOTI3Nzc0Mg==&mid=2247484964&idx=2&sn=c81bce2f26015ee0f9632ddc6c67df03&scene=21#wechat_redirect" target="_blank">尚硅谷 Java 学科全套教程(总 207.77GB)</a> 2. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484192&idx=1&sn=505f2faaa4cc911f553850667749bcbb&scene=21#wechat_redirect" target="_blank">2021 最新版 Java 微服务学习线路图 + 视频</a> 3. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484573&idx=1&sn=7f3d83892186c16c57bc0b99f03f1ffd&scene=21#wechat_redirect" target="_blank">阿里技术大佬整理的《Spring 学习笔记.pdf》</a> 4. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484544&idx=2&sn=c1dfe907cfaa5b9ae8e66fc247ccbe84&scene=21#wechat_redirect" target="_blank">阿里大佬的《MySQL 学习笔记高清.pdf》</a> 5. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485167&idx=1&sn=48d75c8e93e748235a3547f34921dfb7&scene=21#wechat_redirect" target="_blank">2021 版 java 高并发常见面试题汇总.pdf</a> 6. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485664&idx=1&sn=435f9f515a8f881642820d7790ad20ce&scene=21#wechat_redirect" target="_blank">Idea 快捷键大全.pdf</a> ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/1/2883e86e-3eff-404a-8943-0066e5e2b454.png)
#custom-toc-container