读前猜想:
什么是Spring的循环依赖
如何检测是否存在循环依赖
如何解决循环依赖
循环依赖解决要满足哪些条件
什么是早期bean
为什么采用三级缓存解决循环依赖?如果直接将早期bean丢到二级缓存可以么?
什么是Spring的循环依赖 多个bean之间相互依赖,形成了一个闭环
1 2 3 4 5 6 7 8 9 10 11 @Component public class A { @Resource private B b; }@Component public class B { @Resource private A a; }
如何检测是否存在循环依赖 检测循环依赖比较简单,使用一个列表来记录当前正在创建的bean,在每个bean创建之前,先去记录中看一下自己是否在列表中,如果存在,则说明存在循环依赖(A->B->A),如果不在,则将其加入到列表中,待到bean创建完成,再从该列表中移除
单例: spring创建单例bean时候,会调用下面方法
1 2 3 4 5 protected void beforeSingletonCreation (String beanName) { if (!this .inCreationCheckExclusions.contains(beanName) && !this .singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } }
singletonsCurrentlyInCreation就是用来记录目前正在创建中的bean名称列表,this.singletonsCurrentlyInCreation.add(beanName)返回false,说明beanName已经在当前列表中了,此时会抛循环依赖的异常BeanCurrentlyInCreationException
多例 以prototype情况为例,源码位于org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); }if (mbd.isPrototype()) { Object prototypeInstance = null ; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }
Spring如何解决循环依赖的问题 有了前面创建bean的基础后我们知道
Spring创建bean的主要步骤
1、实例化Bean,及调用构造器或者工厂方法创建bean
2、populateBean 填充属性,注入依赖的bean,比如通过set方式、@Autowired注解的方式注入依赖的bean
3、初始化bean,调用初始化方法
从Spring Bean创建的过程来看,注入依赖的方式有两种
实例化bean时通过构造方法注入
填充属性时
构造注入方式导致的循环依赖无法解决 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Component public class ServiceA { private ServiceB serviceB; public ServiceA (ServiceB serviceB) { this .serviceB = serviceB; } }@Component public class ServiceB { private ServiceA serviceA; public ServiceB (ServiceA serviceA) { this .serviceA = serviceA; } }
属性注入方式导致的循环依赖可以解决 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Component public class ServiceA { private ServiceB serviceB; @Autowired public void setServiceB (ServiceB serviceB) { this .serviceB = serviceB; } }@Component public class ServiceB { private ServiceA serviceA; @Autowired public void setServiceA (ServiceA serviceA) { this .serviceA = serviceA; } }
1 2 3 4 5 6 7 8 9 10 1.spring轮询准备创建2个bean:serviceA和serviceB 2.spring容器发现singletonObjects中没有serviceA 3.调用serviceA的构造器创建serviceA实例 4.serviceA准备注入依赖的对象,发现需要通过setServiceB注入serviceB 5.serviceA向spring容器查找serviceB 6.spring容器发现singletonObjects中没有serviceB 7.调用serviceB的构造器创建serviceB实例 8.serviceB准备注入依赖的对象,发现需要通过setServiceA注入serviceA 9.serviceB向spring容器查找serviceA 10.此时又进入步骤2了
完整解决循环依赖的过程:
1.从容器中获取serviceA
2.容器尝试从单例缓存中找serviceA,找不到
3.准备创建serviceA
4.调用serviceA的构造器创建serviceA,得到serviceA实例,此时serviceA还未填充属性,未进行其他任何初始化的操作
5.将早期的serviceA暴露出去:即将其丢到第3级缓存**singletonFactories **中
6.serviceA准备填充属性,发现需要注入serviceB,然后向容器获取serviceB
7.容器尝试从单例缓存中找serviceB,找不到
8.准备创建serviceB
9.调用serviceB的构造器创建serviceB,得到serviceB实例,此时serviceB还未填充属性,未进行其他任何初始化的操作
10.将早期的serviceB暴露出去:即将其丢到第3级缓存singletonFactories中
11.serviceB准备填充属性,发现需要注入serviceA,然后向容器获取serviceA
12.容器尝试从单例缓存中找serviceA,发现此时serviceA位于第3级缓存中,经过处理之后,serviceA会从第3级缓存中移除,然后会存到第2级缓存中,然后将其返回给serviceB,此时serviceA通过serviceB中的setServiceA方法被注入到serviceB中
13.serviceB继续执行后续的一些操作,最后完成创建工作,然后会调用addSingleton方法,将自己丢到第1级缓存中,并将自己从第2和第3级缓存中移除
14.serviceB将自己返回给serviceA
15.serviceA通过setServiceB方法将serviceB注入进去
16.serviceB继续执行后续的一些操作,最后完成创建工作,然后会调用addSingleton方法,将自己丢到第1级缓存中,并将自己从第2和第3级缓存中移除
循环依赖解决要哪些情况下无法解决 1、构造器注入(因为暴露早期对象是对象实例化后进行的)
2、A\B都是多例
3、A多例 B单例懒加载 且容器先获取A时(如果先获取B时可以解决循环依赖的)
4、某些情况下使用了在类方法上使用了@async注解的类(后续再讨论)
什么是早期bean 刚刚实例化好的bean就是早期的bean,此时bean还未进行属性填充,初始化等操作
为什么采用三级缓存解决循环依赖?如果直接将早期bean丢到二级缓存可以么? 为什么使用三级缓存 没看源码之前,有我们自己猜想,可能有一个 HashMap,缓存这我们创建的单例bean,每个创建完成后就往这个bean里放。
1 Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256 );
但是单单如此还解决不了问题,如以下场景
1 2 3 4 5 6 7 8 9 10 1. spring轮询准备创建2 个bean:serviceA和serviceB2. spring容器发现singletonObjects中没有serviceA3. 调用serviceA的构造器创建serviceA实例4. serviceA准备注入依赖的对象,发现需要通过setServiceB注入serviceB5. serviceA向spring容器查找serviceB6. spring容器发现singletonObjects中没有serviceB7. 调用serviceB的构造器创建serviceB实例8. serviceB准备注入依赖的对象,发现需要通过setServiceA注入serviceA9. serviceB向spring容器查找serviceA10. 此时又进入步骤2 了
猜想 既然要解决循环依赖,那么势必有一个Map可以缓存当前创建实例后的bean,以便别的bean在实例化时可以从这个map中获取对象。因此我们需要第二个Map:secondMap来帮我们保存正在创建的bean。
这样的话,我们猜想Spring创建Bean过程如下。
1 2 3 4 5 6 7 8 9 10 11 1. spring轮询准备创建2 个bean:serviceA和serviceB2. spring容器发现2 个map中没有serviceA3. 调用serviceA的构造器创建serviceA实例,并且把serviceA实例添加到secondMap中4. serviceA准备注入依赖的对象,发现需要通过setServiceB注入serviceB5. serviceA向spring容器查找serviceB6. spring容器发现2 个map中没有serviceB7. 调用serviceB的构造器创建serviceB实例,并且把serviceB实例添加到secondMap中8. serviceB准备注入依赖的对象,发现需要通过setServiceA注入serviceA9. serviceB向spring容器查找serviceA,secondMap获取到serviceA10. serviceB创建成功,添加到singletonObjects中,移除secondMap对应实例10. serviceA创建成功,添加到singletonObjects中,移除secondMap对应实例
咋一看,这样设计也不是不行,实际上,spring中也采用类似的方式,稍微有点区别,上面使用了一个缓存,而spring内部采用了3级缓存来解决这个问题,我们一起来细看一下。
3级缓存对应的代码:
1 2 3 4 5 6 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256 );private final Map<String, Object> earlySingletonObjects = new HashMap<>(16 );private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16 );
因此bean从缓存角度实际的创建过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1. 从容器中获取serviceA2. 容器尝试从3 个缓存中找serviceA,找不到3. 准备创建serviceA4. 调用serviceA的构造器创建serviceA,得到serviceA实例,此时serviceA还未填充属性,未进行其他任何初始化的操作5. 将早期的serviceA暴露出去:即将其丢到第3 级缓存singletonFactories中6. serviceA准备填充属性,发现需要注入serviceB,然后向容器获取serviceB7. 容器尝试从3 个缓存中找serviceB,找不到8. 准备创建serviceB9. 调用serviceB的构造器创建serviceB,得到serviceB实例,此时serviceB还未填充属性,未进行其他任何初始化的操作10. 将早期的serviceB暴露出去:即将其丢到第3 级缓存singletonFactories中11. serviceB准备填充属性,发现需要注入serviceA,然后向容器获取serviceA12. 容器尝试从3 个缓存中找serviceA,发现此时serviceA位于第3 级缓存中,经过处理之后,serviceA会从第3 级缓存中移除,然后会存到第2 级缓存中,然后将其返回给serviceB,此时serviceA通过serviceB中的setServiceA方法被注入到serviceB中13. serviceB继续执行后续的一些操作,最后完成创建工作,然后会调用addSingleton方法,将自己丢到第1 级缓存中,并将自己从第2 和第3 级缓存中移除14. serviceB将自己返回给serviceA15. serviceA通过setServiceB方法将serviceB注入进去16. serviceB继续执行后续的一些操作,最后完成创建工作,然后会调用addSingleton方法,将自己丢到第1 级缓存中,并将自己从第2 和第3 级缓存中移除
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 26 27 28 29 30 31 32 33 34 35 protected Object getSingleton (String beanName, boolean allowEarlyReference) { Object singletonObject = this .singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this .singletonObjects) { singletonObject = this .earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this .singletonFactories.get(beanName); if (singletonFactory != null ) { singletonObject = singletonFactory.getObject(); this .earlySingletonObjects.put(beanName, singletonObject); this .singletonFactories.remove(beanName); } } } } return singletonObject; }protected void addSingleton (String beanName, Object singletonObject) { synchronized (this .singletonObjects) { this .singletonObjects.put(beanName, singletonObject); this .singletonFactories.remove(beanName); this .earlySingletonObjects.remove(beanName); } }
解答 那回归正题,如上所述,为什么Spring要用3级缓存来解决循环依赖呢?如果只使用2级缓存,直接将刚实例化好的bean暴露给二级缓存出是否可以?
结论:不能
原因:这样做是可以解决:早期暴露给其他依赖者的bean和最终暴露的bean不一致的问题。
若将刚刚实例化好的bean直接丢到二级缓存中暴露出去,如果后期这个bean对象被更改了,比如可能在上面加了一些拦截器,将其包装为一个代理了,那么暴露出去的bean和最终的这个bean就不一样的,将自己暴露出去的时候是一个原始对象,而自己最终却是一个代理对象,最终会导致被暴露出去的和最终的bean不是同一个bean的,将产生意向不到的效果,而三级缓存就可以发现这个问题,会报错。
举例说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Component public class Service1 { public void m1 () { System.out.println("Service1 m1" ); } private Service2 service2; @Autowired public void setService2 (Service2 service2) { this .service2 = service2; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Component public class Service2 { public void m1 () { System.out.println("Service2 m1" ); this .service1.m1(); } private Service1 service1; @Autowired public void setService1 (Service1 service1) { this .service1 = service1; } public Service1 getService1 () { return service1; } }
注意上面的@1,service2的m1方法中会调用service1的m1方法。
需求 在service1上面加个拦截器,要求在调用service1的任何方法之前需要先输出一行日志:”你好,service1”
实现 新增一个Bean后置处理器来对service1对应的bean进行处理,将其封装为一个代理暴露出去。
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 26 27 28 29 import org.springframework.aop.MethodBeforeAdvice;import org.springframework.aop.framework.ProxyFactory;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanPostProcessor;import org.springframework.lang.Nullable;import org.springframework.stereotype.Component;import java.lang.reflect.Method;@Component public class MethodBeforeInterceptor implements BeanPostProcessor { @Nullable @Override public Object postProcessAfterInitialization (Object bean, String beanName) throws BeansException { if ("service1" .equals(beanName)) { ProxyFactory proxyFactory = new ProxyFactory(bean); proxyFactory.addAdvice(new MethodBeforeAdvice() { @Override public void before (Method method, Object[] args, @Nullable Object target) throws Throwable { System.out.println("你好,service1" ); } }); return proxyFactory.getProxy(); } return bean; } }
上面的postProcessAfterInitialization方法内部会在service1初始化之后调用,内部会对service1这个bean进行处理,返回一个代理对象,通过代理来访问service1的方法,访问service1中的任何方法之前,会先输出:你好,service1。
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration @ComponentScan(value = {"com.study.cycledepend.thirdlevelcache"}) public class AopConfig { }public class MainClass { public static void main (String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(AopConfig.class); context.refresh(); } }
运行:报错了 可以看出是AbstractAutowireCapableBeanFactory.java:624这个地方整出来的异常,将这块代码贴出来给大家看一下。
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 26 27 28 29 if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false ); if (earlySingletonReference != null ) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this .allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] 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 " + "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example." ); } } } }
我们再来通过代码级别的来解释上面代码:
这段代码主要用来判断当有循环依赖的情况下,早期暴露给别人使用的bean是否和最终的bean不一样的情况下,会抛出一个异常。
@1:调用getSingleton(beanName, false)方法,这个方法用来从3个级别的缓存中获取bean,但是注意了,这个地方第二个参数是false,此时只会尝试从第1级和第2级缓存中获取bean,如果能够获取到,说明了什么?说明了第2级缓存中已经有这个bean了,而什么情况下第2级缓存中会有bean?说明这个bean从第3级缓存中已经被别人获取过,然后从第3级缓存移到了第2级缓存中,说明这个早期的bean被别人通过getSingleton(beanName, true)获取过
@2:这个地方用来判断早期暴露的bean和最终spring容器对这个bean走完创建过程之后是否还是同一个bean,上面我们的service1被代理了,所以这个地方会返回false,此时会走到@3
@3:allowRawInjectionDespiteWrapping这个参数用来控制是否允许循环依赖的情况下,早期暴露给被人使用的bean在后期是否可以被包装,通俗点理解就是:是否允许早期给别人使用的bean和最终bean不一致的情况,这个值默认是false,表示不允许,也就是说你暴露给别人的bean和你最终的bean需要是一直的,你给别人的是1,你后面不能将其修改成2了啊,不一样了,你给我用个鸟。
而上面代码注入到service2中的service1是早期的service1,而最终spring容器中的service1变成一个代理对象了,早期的和最终的不一致了,而allowRawInjectionDespiteWrapping又是false,所以报异常了。
如何解决 很简单,将allowRawInjectionDespiteWrapping设置为true就可以了,下面改一下代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MainClass { public static void main (String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.addBeanFactoryPostProcessor(beanFactory -> { if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true ); } }); context.register(IocConfig.class); context.refresh(); System.out.println("容器初始化完毕" ); }
上面代码中将allowRawInjectionDespiteWrapping设置为true了,是通过一个BeanFactoryPostProcessor来实现的,后面会有一篇文章来详解BeanFactoryPostProcessor,目前你只需要知道BeanFactoryPostProcessor可以在bean创建之前用来干预BeanFactory的创建过程,可以用来修改BeanFactory中的一些配置。
这样配置虽然不报错了,但是spring容器中的Service1和Service2实例中的service1仍旧不是同一个对象,那么这种情况是不是很诧异,如何解决这个问题?
既然最终service1是一个代理对象,那么你提前暴露出去的时候,注入到service2的时候,你也必须得是个代理对象啊,需要确保给别人和最终是同一个对象。
这个怎么整?继续看暴露早期bean的源码,注意了下面是重点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));protected Object getEarlyBeanReference (String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }
我们可以自定义一个SmartInstantiationAwareBeanPostProcessor,然后在其getEarlyBeanReference中来创建代理不就可以了,聪明,我们来试试,将MethodBeforeInterceptor代码改成下面这样:
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 26 27 28 29 30 31 32 33 @Component public class MethodBeforeInterceptor implements SmartInstantiationAwareBeanPostProcessor { @Override public Object getEarlyBeanReference (Object bean, String beanName) throws BeansException { if ("service1" .equals(beanName)) { ProxyFactory proxyFactory = new ProxyFactory(bean); proxyFactory.addAdvice(new MethodBeforeAdvice() { @Override public void before (Method method, Object[] args, Object target) throws Throwable { System.out.println("你好,service1" ); } }); return proxyFactory.getProxy(); } return bean; } }public class MainClass1 { public static void main (String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class); Service1 service1 = context.getBean(Service1.class); Service2 service2 = context.getBean(Service2.class); service1.m1(); service2.m1(); System.out.println(service1.equals(service2.getService1())); } }
1 2 3 4 5 6 你好,service1 Service1 m1 Service2 m1 你好,service1 Service1 m1true
这样就解决问题了。
猜想 1、@Async循环依赖如何解决? 2、SpringAOP、事务是否会导致早期暴露bean不一致? 3、开放性问题:为什么@EnableAsync不像AOP和事务一样实现AbstractAdvisorAutoProxyCreator(实现了SmartInstantiationAwareBeanPostProcessor)这个接口呢?异步事务按照AOP那样生效是不是就可以避免循环依赖呢?
1、标注了@Async注解方法的类A,在注入其他组件时B,@lazy,懒加载该类A,因此创建B时不会实时创建A,避免破坏早期bean,避免循环依赖
2、@EnableAspectJAutoProxy -> AspectJAutoProxyRegistrar ->AnnotationAwareAspectJAutoProxyCreator ->AbstractAutoProxyCreator(实现了SmartInstantiationAwareBeanPostProcessor)(可在获取早期对象时就生成代理对象)
@EnableAsyncProxyAsyncConfiguration ->AsyncAnnotationBeanPostProcessor(没有实现SmartInstantiationAwareBeanPostProcessor) (在postProcessAfterInitialization这个阶段才能触发生成代理对象)
3、不可估量