为什么三级缓存没有彻底解决循环依赖?

三级缓存解决循环依赖的故事相信很多人都听过, 但是我们项目中还是偶尔能遇到循环依赖报出来的错, 那么三级缓存到底有没有解决循环依赖, 什么场景下的循环依赖又不能解决, 我们本节来探究探究(不涉及构造器循环)。

版本声明

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我们不讨论

在这里插入图片描述

简单小结一下

  1. 当前bean添加到创建bean的队列中
  2. 通过构造器实例化bean对象
  3. 如果允许循环依赖, 会将创建早期bean的动作封装到一个工厂, 并添加到三级缓存中。因为只有真正发生循环依赖才需要调用工厂获取早期bean(只经过实例化, 没有初始化内部属性和一些初始化方法的bean)
  4. 注入属性(populateBean); @Value@Autowire会在这里处理; 循环依赖的对象也会在这里注入
  5. 处理初始化(initializeBean), 这里很重要, 它不仅会处理例如@PostConstructAware接口, 还有可能通过后置处理器生成代理对象, 导致实例化的bean和初始化之后的bean不相等; 这就是循环依赖报错的关键
  6. 将当前bean从创建bean的队列中移除
  7. 最后将创建好并且注入属性、初始化完成的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;
}

那么它的创建流程是这样的
在这里插入图片描述

流程小结

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

我们再来看service1创建之后的动作

在这里插入图片描述

此时满足如下条件

  1. 第2级缓存中已经有了service1早期的bean(因为servcie2依赖servcie1在getBean的时候调用了第三级缓存将service1的早期bean添加到了第2级缓存),
  2. 实例化的bean与初始化之后(initializeBean)的bean是同一个

因为第二点实例化的bean与初始化之后(initializeBean)的bean是同一个, 所以没有循环依赖报错

然后就是

  1. 将创建的bean添加到一级缓存

  2. 从二级缓存中删除

  3. 从三级缓存中删除

这就是常说的三级缓存解决循环依赖

创建异常循环依赖的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;
}

小结

  1. @EnableAsync注解import了一个AsyncAnnotationBeanPostProcessor的bean后置处理器, 在该bean中创建了一个PointcutAdvisor类型的advisor(AsyncAnnotationAdvisor)
  2. service1在注入属性的时候, 调用后置bean处理器AsyncAnnotationBeanPostProcessorpostProcessAfterInitialization方法(在父类AbstractAdvisingBeanPostProcessor中)
  3. 初始化后置处理方法postProcessAfterInitialization中根据AsyncAnnotationAdvisor判断当前bean(service1)是一个早期暴露的bean(存在方法上有@Async注解)
  4. 如果满足第3点后, 这里就会创建一个新的代理对象

到这里就看到了, servcie1创建过程中, 在initializeBean进行初始化动作的时候, 由于有@Async注解标识的方法, 导致生成了一个新的代理对象, 记得这一点, 我们再结合上面的图来分析一下

在这里插入图片描述

此时满足如下条件

  1. 第2级缓存中已经有了service1早期的bean(因为servcie2依赖servcie1在getBean的时候调用了第三级缓存将service1的早期bean添加到了第2级缓存),
  2. 实例化的bean与初始化之后(initializeBean)的bean不是同一个
  3. 允许注入早期的bean
  4. 有其它bean依赖service1(service2依赖了它)
  5. 报循环依赖异常

两种循环依赖对比

相中循环依赖的整体流程基本都是一样的, 不同点是当开启了@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来解决循环依赖, 最好直接不使用这种循环依赖

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

uncleqiao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值