三级缓存解决循环依赖的故事相信很多人都听过, 但是我们项目中还是偶尔能遇到循环依赖报出来的错, 那么三级缓存到底有没有解决循环依赖, 什么场景下的循环依赖又不能解决, 我们本节来探究探究(不涉及构造器循环)。
版本声明
spring 6.1.8
三级缓存对象
spring中有三个本地缓存用来存储bean创建过程中不同状态的对象, 分别用三个map存储, 它们分别是如下三个对象
1、singletonObjects
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
一级缓存; 存的是已经完全准备好的bean(走完了整个创建流程的bean,包含实例化、初始化、注入等步骤)
2、earlySingletonObjects
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
二级缓存; 存的是只实例化但是没有初始化状态的bean, 被称作early bean(早期的bean)
3、singletonFactories
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
三级缓存; 存放的是获取early bean的工厂(二级缓存早期bean的工厂).
获取bean
接下来看一下从三级缓存中获取bean的一段流程

当我们创建一个普通bean的时候, 是先从一级缓存中拿bean, 并且未空, 然后判断当前bean不是在创建中的, 自然也还未开始创建, 所以正常情况下不会走到从二级或者三级缓存中去拿。接下来就走创建bean的流程
创建普通单例bean
这里以普通单例bean为例, 工厂、父容器、多例、自定义作用域的bean我们不讨论

简单小结一下
- 当前bean添加到创建bean的队列中
- 通过构造器实例化bean对象
- 如果允许循环依赖, 会将创建早期bean的动作封装到一个工厂, 并添加到三级缓存中。因为只有真正发生循环依赖才需要调用工厂获取早期bean(只经过实例化, 没有初始化内部属性和一些初始化方法的bean)
- 注入属性(populateBean);
@Value和@Autowire会在这里处理; 循环依赖的对象也会在这里注入。 - 处理初始化(initializeBean), 这里很重要, 它不仅会处理例如
@PostConstruct、Aware接口, 还有可能通过后置处理器生成代理对象, 导致实例化的bean和初始化之后的bean不相等; 这就是循环依赖报错的关键 - 将当前bean从创建bean的队列中移除
- 最后将创建好并且注入属性、初始化完成的bean添加到一级缓存中, 从二级缓存和三级缓存中移除
这就是一个普通单例bean创建的流程, 其中忽略了很多步骤, 例如构造器的选择, 后置处理器的处理等, 但是与本文关系不大。
创建正常循环依赖的bean
上面介绍了一个普通的单例bean的创建过程, 这里我们看看循环依赖下bean的创建;
有个前提是我们没有配置allowCircularReferences属性, 或者配置了它为true
spring.main.allow-circular-references=true
我们现在有这么两个servie
@Component
public class Service1 {
@Autowired
private Service2 service2;
}
@Component
public class Service2 {
@Autowired
private Service1 service1;
}
那么它的创建流程是这样的

流程小结
- service1创建时, 由于开启了允许循环引用, 会将创建早期bean的动作添加到第三级缓存中
- 在注入属性(populateBean)时, 会调用getBean(service2)
- 同样service2在注入属性(populateBean)时, 会调用getBean(service1), 此时
- service1正在创建中
- 允许获取早起的bean(默认为true)
- 二级缓存中还没有service1对象
- 调用第三级缓存的工厂bean, 获取service1早期的bean(未被初始化)
- 将早期bean添加到二级缓存中, 并删除三级缓存中的工厂对象
- 此时service2就创建成功了, service1也完成了创建
我们再来看service1创建之后的动作

此时满足如下条件
- 第2级缓存中已经有了service1早期的bean(因为servcie2依赖servcie1在getBean的时候调用了第三级缓存将service1的早期bean添加到了第2级缓存),
- 实例化的bean与初始化之后(initializeBean)的bean是同一个
因为第二点实例化的bean与初始化之后(initializeBean)的bean是同一个, 所以没有循环依赖报错
然后就是
-
将创建的bean添加到一级缓存
-
从二级缓存中删除
-
从三级缓存中删除
这就是常说的三级缓存解决循环依赖
创建异常循环依赖的bean
我们现在对servcie1进行改造一下
@Component
@EnableAsync
public class Service1 {
@Autowired
private Service2 service2;
@Async
public void test1() {
System.out.println("Service1.test1()");
}
}
@Component
public class Service2 {
@Autowired
private Service1 service1;
}
启动直接报错
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'service1': Bean with name 'service1' has been injected into other beans [service2] 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.
那么很显然是@EnableAsync+@Async注解引起的问题, 接下来我们探究下具体原因
1、现在我们看到初始化方法initializeBean
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
// 1.生命周期方法aware
invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
// 2.初始化之前的回调
if (mbd == null || !mbd.isSynthetic()) {
// ImportAware 和 @PostConstruct指定的init方法的处理
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// 3.afterPropertiesSet方法 和 mbd中指定的init方法
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);
}
// 4.初始化之后的回调(非合成beanDefinition)
if (mbd == null || !mbd.isSynthetic()) {
// 初始化之后的处理(代理就是这里创建的)
// (如果循环引用的时候通过从第三级缓存中拿到对象并创建代理后, 这里就不需要创建代理对象了)
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
2、在完成一些初始化操作后, 会调用到applyBeanPostProcessorsAfterInitialization, 该方法会调用BeanPostProcessor#postProcessAfterInitialization初始化之后的后置处理器
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
// AbstractAutoProxyCreator 中 创建代理对象
// AbstractAdvisingBeanPostProcessor中如果定义了advisor, 这里会添加自定义的Advisor创建代理(可能生成代理后再次代理)
// ApplicationListenerDetector 中将定义的ApplicationListener添加到容器中(广播器中也有)
// ScheduledAnnotationBeanPostProcessor 处理了Scheduled/Schedules注解
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
3、其中@EnableAsync注解import了一个AsyncAnnotationBeanPostProcessor的bean后置处理器, 我们看看它的实现
public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {
@Override
public void setBeanFactory(BeanFactory beanFactory) {
super.setBeanFactory(beanFactory);
// PointcutAdvisor类型的advisor
AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
if (this.asyncAnnotationType != null) {
advisor.setAsyncAnnotationType(this.asyncAnnotationType);
}
advisor.setBeanFactory(beanFactory);
this.advisor = advisor;
}
}
// 父类AbstractAdvisingBeanPostProcessor中的初始化后置处理如下
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 如果自定义了advisor, 把它添加到advisor链中重新生成代理
if (this.advisor == null || bean instanceof AopInfrastructureBean) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
// 当前bean是Advised类型
if (bean instanceof Advised advised) {
...
}
// 到这里就是非Advised类型的bean
// 判断当前的advisor是否能处理当前bean
if (isEligible(bean, beanName)) {
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
// 没有配置需要代理目标类
if (!proxyFactory.isProxyTargetClass()) {
// 完善proxyFactory对象; 实现了接口就添加代理接口 没有则添加 proxyTargetClass=true的标识
evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
customizeProxyFactory(proxyFactory);
// Use original ClassLoader if bean class not locally loaded in overriding class loader
ClassLoader classLoader = getProxyClassLoader();
if (classLoader instanceof SmartClassLoader smartClassLoader &&
classLoader != bean.getClass().getClassLoader()) {
classLoader = smartClassLoader.getOriginalClassLoader();
}
// 创建代理对象
return proxyFactory.getProxy(classLoader);
}
// No proxy needed.
// 实例化时候不创建代理
return bean;
}
小结
@EnableAsync注解import了一个AsyncAnnotationBeanPostProcessor的bean后置处理器, 在该bean中创建了一个PointcutAdvisor类型的advisor(AsyncAnnotationAdvisor)- service1在注入属性的时候, 调用后置bean处理器
AsyncAnnotationBeanPostProcessor的postProcessAfterInitialization方法(在父类AbstractAdvisingBeanPostProcessor中) - 初始化后置处理方法
postProcessAfterInitialization中根据AsyncAnnotationAdvisor判断当前bean(service1)是一个早期暴露的bean(存在方法上有@Async注解) - 如果满足第3点后, 这里就会创建一个新的代理对象
到这里就看到了, servcie1创建过程中, 在initializeBean进行初始化动作的时候, 由于有@Async注解标识的方法, 导致生成了一个新的代理对象, 记得这一点, 我们再结合上面的图来分析一下

此时满足如下条件
- 第2级缓存中已经有了service1早期的bean(因为servcie2依赖servcie1在getBean的时候调用了第三级缓存将service1的早期bean添加到了第2级缓存),
- 实例化的bean与初始化之后(initializeBean)的bean不是同一个
- 允许注入早期的bean
- 有其它bean依赖service1(service2依赖了它)
- 报循环依赖异常
两种循环依赖对比
相中循环依赖的整体流程基本都是一样的, 不同点是当开启了@EnableAsync, 并且service1中某个非静态public方法添加了@Async注解后, 在初始化过程中(initializeBean)会生成一个代理对象, 导致实例化创建的bean与当前bean不是同一个对象了。
那么为什么当前实例化之后的bean被重新创建一个新的bean后, 在循环依赖的场景下要抛异常呢?
-
试想一下, 如果没有循环依赖, 也就是只有service1依赖service2, 或者只有service2依赖service1, 那么service1不管被生成几次, 只要是创建的结果只有一个bean就行。
-
那么当形成循环依赖后, service2注入(
populateBean方法中)的service1是第一次实例化创建出来的(第三级缓存中获取到的),而service1完成对service2的注入后(populateBean)方法之后, 在初始化方法中(initializeBean)重新生成了一个对象, 此时service2注入的对象和当前service1就不是一个对象了, 而当前对象要注入到springIOC容器中的, 那就不是单例了哈。
避免循环依赖报错
1、允许注入早期(未完成的初始化的bean)
AnnotationConfigApplicationContext acContext = new AnnotationConfigApplicationContext();
if (acContext.getBeanFactory() instanceof AbstractAutowireCapableBeanFactory aac) {
// 允许注入原始类, 就算会生成代理类
aac.setAllowRawInjectionDespiteWrapping(true);
}
这种不建议, 一个对象可能有多个bean的形式存在
2、使用@Lazy注解(推荐)
@Lazy
@Autowired
private Service2 service2;
在ContextAnnotationAutowireCandidateResolver#buildLazyResolutionProxy中为懒加载的注入对象生成一个代理公工厂ProxyFactory
3、直接使用工厂包装被依赖项
@Autowired
private ObjectFactory<Service2> service2ObjFactory;
@Autowired
private ObjectProvider<Service2> service2ObjProvider;
@Async
public void test1() {
Service2 service2 = service2ObjFactory.getObject();
Service2 service2 = service2ObjProvider.getIfAvailable();
}
详见 DefaultListableBeanFactory#resolveDependency;
如果是在@Bean的方法参数中用这种还是可以的, 在一般开发场景下尽量也别用, 因为每次注入后都要调用getObject/getIfAvailable会比较麻烦
总结
到现在我们知道了普通的循环依赖, 以及异常场景的循环依赖, 那么什么情况下会发生呢?
1、如果当前对象A与其它对象B形成了A-B-A甚至更长的循环依赖, 并且A还将生成代理对象(或者自定义对象), 导致注入到依赖A的那个对象与实例化B的那个对象不是同一个, 就会有这个问题
2、常见的有@EnableAsync+@Async, @Validated, @Publisher, @Repository, 它们都是由AbstractAdvisingBeanPostProcessor类型的BeanPostProcessor注入的
3、使用@Lazy注解, 或者ObjectFactory/ObjectProvider来解决循环依赖, 最好直接不使用这种循环依赖

被折叠的 条评论
为什么被折叠?



