spring三级缓存以及@Async产生循环引用

spring三级缓存介绍

三级缓存解除循环引用原理

A => B => A
一级缓存 singletonObjects
二级缓存 earlySingletonObjects
三级缓存 singletonFactories

1、获取A,从三级缓存中获取,没有获取到
2、构造A,将A置入三级缓存
3、注入属性,构造B
4、获取B,从三级缓存中没有获取到
5、构造B,将B置入三级缓存
6、注入属性,构造A
7、获取A,从三级缓存中获取,一级没有、二级没有、三级存在。此时通过从三级的BeanFactory构造实例对象,放入二级缓存,移除三级缓存
8、创建B实例后续步骤
9、B构造完成,实例放入一级缓存,二级三级缓存移除
10、创建A实例后续步骤
11、A构造完成,实例放入一级缓存,二级三级缓存移除

源码对应

springboot 2.5.6

1、获取A,从三级缓存中获取,没有获取到

// AbstractBeanFactory#doGetBean   256行
protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {
String beanName = transformedBeanName(name);
Object beanInstance;

// 从三个缓存中依次获取,注意方法点进去allowEarlyReference是true
Object sharedInstance = getSingleton(beanName);
		...

2、构造A,将A置入三级缓存

构造A(创建A实例)
// AbstractAutowireCapableBeanFactory#doCreateBean   582行
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
	instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 这里会通过构造函数创建Bean实例
	instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//省略代码...
置入缓存
// AbstractAutowireCapableBeanFactory#doCreateBean  613行
//省略代码...
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
	if (logger.isTraceEnabled()) {
		logger.trace("Eagerly caching bean '" + beanName +
				"' to allow for resolving potential circular references");
	}
	// 这里会把生成beanName的BeanFactory置入三级缓存
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
//省略代码...

3、注入属性,构造B

扫描缓存实例的相关信息

InitDestroyAnnotationBeanPostProcessor会解析缓存类中@PostStruct@PreDestroy等相关信息
CommonAnnotationBeanPostProcessor会解析缓存类中@Recource@WebServiceRef@PostStruct@PreDestroy等相关信息
AutowiredAnnotationBeanPostProcessor会解析缓存类中@Autowired@Value@Inject等相关信息

// AbstractAutowireCapableBeanFactory#doCreateBean 594行
//省略代码...
synchronized (mbd.postProcessingLock) {
	if (!mbd.postProcessed) {
		try {
		// 解析缓存注解相关信息
			applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Post-processing of merged bean definition failed", ex);
		}
		mbd.postProcessed = true;
	}
}
//省略代码...

注入属性
// AbstractAutowireCapableBeanFactory#doCreateBean 619行
//省略代码...
try {
	// 这里会将注入对应的属性
	populateBean(beanName, mbd, instanceWrapper);
	exposedObject = initializeBean(beanName, exposedObject, mbd);
}
//省略代码...

// AbstractAutowireCapableBeanFactory#populateBean 1431行
//省略代码...
// 这里会依次遍历 如果是@Resource会在CommonAnnotationBeanPostProcessor中触发注入
// 如果是@Autowired则会在AutowiredAnnotationBeanPostProcessor中触发注入
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
	PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
	if (pvsToUse == null) {
		if (filteredPds == null) {
			filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
		}
		pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
		if (pvsToUse == null) {
			return;
		}
	}
	pvs = pvsToUse;
}
//省略代码...

4、获取B,从三级缓存中没有获取到

5、构造B,将B置入三级缓存

6、注入属性,构造A

上面三个其实类似,同前面三步骤差不多

7、获取A,三个缓存中三级缓存命中

 // AbstractBeanFactory#doGetBean   256行  入口
// DefaultSingletonBeanRegistry#getSingleton  150
// 此时,一级缓存和二级缓存里面都没有,三级缓存在第二步里面放进去了,此时能拿到对应的ObjectFactory,
//通过它可以获取对应的实例。然后将实例放入二级缓存,将三级缓存中对应的BeanName移除
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		singletonObject = this.earlySingletonObjects.get(beanName);
		if (singletonObject == null && allowEarlyReference) {
			synchronized (this.singletonObjects) {
				// Consistent creation of early reference within full singleton lock
				singletonObject = this.singletonObjects.get(beanName);
				if (singletonObject == null) {
					singletonObject = this.earlySingletonObjects.get(beanName);
					if (singletonObject == null) {
						ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
						if (singletonFactory != null) {
							singletonObject = singletonFactory.getObject();
							this.earlySingletonObjects.put(beanName, singletonObject);
							this.singletonFactories.remove(beanName);
						}
					}
				}
			}
		}
	}
	return singletonObject;
}

8、创建B实例后续步骤

@Async循环引用触发点

这里属性注入完后,剩下bean增强、循环依赖校验、注册DisposableBean

9、B构造完成,实例放入一级缓存,二级三级缓存移除

// DefaultSingletonBeanRegistry#getSingleton  259行
//省略代码...
// 这在执行链路的上面几层。这里判断是否是新的单例,如果是则置入缓存(一级缓存)中
if (newSingleton) {
	addSingleton(beanName, singletonObject);
}
//省略代码...

protected void addSingleton(String beanName, Object singletonObject) {
// 放入一级缓存,从二级缓存和三级缓存中移除
	synchronized (this.singletonObjects) {
		this.singletonObjects.put(beanName, singletonObject);
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}

10、创建A实例后续步骤

11、A构造完成,实例放入一级缓存,二级三级缓存移除

逻辑基本同上

@Async导致三级缓存没有解除循环引用

复现

测试类

@Service
@Slf4j
public class AService {

    static {
        log.info("AService init===>");
    }

    @Resource
    private BService bService;

    public String a2bService(){
        return bService.get();
    }

    @Async("async-2")
    public void exec(){
        log.info("aService exec");
    }

}
@Service
@Slf4j
public class BService {

    static {
        log.info("BService init===>");
    }

    @Autowired
    private AService aService;

    public String get(){
        return "BService 666";
    }
    
    public void exec(){
        log.info("BService exec");
    }

}

描述

这里AServiceBService在同一个目录下,也没有被在别的类中注入(如果有一个类把两者都注入了,且Bservice先注入,此时如果三个类中该类最先注入,就会导致Bservice先初始化)。在这种情况下,默认就会Aservice先初始化,然后在Bservice初始化,如此,才能复现问题。
如果想要调整类加载顺序,可以通过@DependsOn(value = "AService")或者@Lazy

备注

别开启懒加载,不然启动不会报错。
spring.main.lazy-initialization=true

启动测试

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AService': Bean with name 'AService' has been injected into other beans [BService] 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.
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:649)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:338)
	at com.yichen.casetest.CaseTestApplication.main(CaseTestApplication.java:47)

原因分析

在bean实例后续步骤中,会对bean增强

// AbstractAutowireCapableBeanFactory#doCreateBean  620行
try {
	populateBean(beanName, mbd, instanceWrapper);
	// 初始化bean,对Bean增强
	// 1、invokeAwareMethods
	// 2、applyBeanPostProcessorsBeforeInitialization
	// 3、invokeInitMethods
	// 4、applyBeanPostProcessorsAfterInitialization
	exposedObject = initializeBean(beanName, exposedObject, mbd);
}

applyBeanPostProcessorsAfterInitialization中,由于使用了异步线程池(用了@EnableAsync),使得AsyncAnnotationBeanPostProcessor注入了spring容器,它会为原有的bean实例创建CGLIB代理,使得最初的bean和实例化后暴露出去的bean不是同一个,没有通过循环引用校验,抛出了异常。

// AsyncAnnotationBeanPostProcessor的子类
//AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization  86行
// 如果类中有@Async会创建代理
if (isEligible(bean, beanName)) {
	ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
	if (!proxyFactory.isProxyTargetClass()) {
		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 && classLoader != bean.getClass().getClassLoader()) {
		classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
	}
	return proxyFactory.getProxy(classLoader);
}
// AbstractAutowireCapableBeanFactory#doCreateBean  632行
// 循环引用检测
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 " +
						"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
			}
		}
	}
}

在这里插入图片描述

解决方法

1、不用异步线程池,自己实现线程池
2、通过@lazy@DependsOn,调整类加载顺序,如果让Bservice先加载就不会出错
3、梳理业务逻辑,调整技术实现,让AServiceBservice不循环引用

参考

Using @Async in a circular dependency will throw a BeanCurrentlyInCreationException

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值