spring对于循环依赖在内部已经有解决方案了,比较经典的就是去解决代理对象的循环依赖问题,但是spring并不能完全能够对各种代理场景都能够保证循环依赖,比如异步代理的循环依赖就会报错,我们下面先来演示一下
报错复原
@Component
public class A implements C {
@Autowired
private D d;
@Async
@Override
public void a() {
}
}
@Component
public class B implements D {
@Autowired
private C c;
@Override
public void b(){
}
}
上面有两个类一个是A,一个是B,其中A和B产生了循环依赖,并且A的a方法加上了@Async注解,所以按照道理来说A应该是会变成一个异步切面的代理对象,我们来执行一下
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
结果:
可以发现报的错是BeanCurrentlyInCreationException,也就是循环依赖报错了,这时候我们可能就有点疑惑,spring不是已经解决了循环依赖了吗,为什么还会报循环依赖的错?
当我们把A类的a方法上面的@Async注解去掉的时候,就又会发现没有报循环依赖的错了
普通代理对象循环依赖的过程
我们先去通过普通代理对象的循环依赖去简单地回顾下spring是如何解决代理对象的循环依赖的,我们按照A是加了@Transactional注解的一个类,B是一个普通类这种场景去看,那么spring实例化的顺序应该是实例化A->getBean(B)->实例化B -> getBean(A),那么在第一次实例化A的过程中,会提前暴露出一个A的对象工厂
首先来看这个提前暴露的对象工厂
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");
}
// 提前暴露出一个对象工厂,这个对象工厂能够拿到该实例的代理对象(除异步切面代理)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
// 如果一级缓存不存在这个bean
if (!this.singletonObjects.containsKey(beanName)) {
// 把这个bean的对象工厂放到二级缓存中
this.singletonFactories.put(beanName, singletonFactory);
// 移除这个bean的三级缓存
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
为什么提前暴露出一个对象工厂呢?如果大家熟悉循环依赖的话就会知道,当B实例化去进行属性注入的时候,此时第二次去实例化A,那么就需要这个对象工厂返回这个A最终形态的一个对象,比如A的代理对象,因为此时第一次实例化A还没有完成,所以就只能通过这个提前暴露的对象工厂去直接拿到A的代理对象,那么A的对象工厂是在什么时候被调用的呢?答案就是当第二次实例化A的时候会被调用,此时实例化A会进来getSingleton方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
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;
}
上面的代码会先去singletonObjects一级缓存中去寻找是否有A,很明显A还没实例化完所以肯定返回的是null,接着会再去二级缓存中拿,二级缓存放的是A的对象工厂返回的对象,而此时A的对象工厂还没被调用所以返回的也是null,最后会从三级缓存中找,也就是A的对象工厂,对象工厂返回的对象再保存到二级缓存中,那么现在再来看A的对象工厂是怎么返回的
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;
}
这个对象工厂返回的是经过一系列BeanPostProcessor处理得到的对象,并且这个BeanPostProcessor必须是SmartInstantiationAwareBeanPostProcessor类型的,由于我们的场景是A加了@Transactional事务注解,当A生成事务代理对象的时候肯定是依赖负责处理事务代理的BeanPostProcessor,也就是InfrastructureAdvisorAutoProxyCreator,InfrastructureAdvisorAutoProxyCreator继承的是AbstractAutoProxyCreator,而AbstractAutoProxyCreator又是SmartInstantiationAwareBeanPostProcessor类型的,所以我们直接去看AbstractAutoProxyCreator的getEarlyBeanReference方法
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
getEarlyBeanReference方法会去把beanName以及bean放到earlyProxyReferences这个map中,然后去执行wrapIfNecessary方法返回事务代理对象,而wrapIfNecessary这个方法就不多说了,里面其实就是去找是否有适用于该bean的advisor,判断是否适用的依据就是通过绑定在advisor里面的pointcut去对类以及方法进行match。当对象工厂执行完成之后,第二次A的实例化也就结束了,然后会到B的实例化,最后会到第一次A的实例化过程,此时A已经完成了对B的属性注入了,然后就会去执行一系列的BeanPostProcessor,就会来到AbstractAutoProxyCreator的后置处理方法
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
可以看到在后置处理方法里面会去earlyProxyReferences这个map中去根据beanName来remove里面的值,前面在第二次实例化A的过程中,在调用A的对象工厂的时候就已经把A这个bean放到earlyProxyReferences中了,所以此时remove返回到的对象就是A的原始对象,所以后置处理器的wrapIfNecessary方法不会被调用,接着后置处理器执行完毕,继续来到下面的代码
// 注意这里,如果该bean使用了spring的异步调用并且该bean存在循环依赖会报错
if (earlySingletonExposure) {
// 如果当前实例化的bean存在与其他的bean产生循环依赖的情况,那么返回的earlySingletonReference就是当前实例化bean提前暴露的工厂返回的对象
// 如果当前实例化的bean不与任何bean产生循环依赖的情况,那么返回的earlySingletonReference等于null
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// if条件满足,证明存在循环依赖,判断当前bean经过后置处理器处理之后的结果与自身原始的对象是否相等
if (exposedObject == bean) {
// 如果相等则返回提前暴露的工厂返回的对象
exposedObject = earlySingletonReference;
}
// 如果不相等,什么时候不相等?
// 比如该bean使用了异步调用并且与其他bean产生循环依赖,最终该bean经过后置处理器之后得到的就是一个异步调用的代理对象
// 为什么其他的代理对象产生就不会发生报错?比如aop,事务,缓存?
// 因为其他的代理对象产生的方式,都可以通过提前暴露的工厂得到,最终把代理对象放到二级缓存中,但是异步调用产生代理对象的方式与前面几种并不一样
// 提前暴露的工厂并不能得到异步调用产生的代理对象,因为异步调用产生的代理对象是通过另外一个BeanPostProcessor去产生的,而其他的代理对象产生是都是通过AbstractAutoProxyCreator去处理的
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.");
}
}
}
}
上面的这一段代码就是会去从一级或者二级缓存中获取A,很明显一级缓存是没有的,而二级缓存在第二次实例化A的时候通过A的对象工厂返回了A的事务代理对象并且缓存到了二级缓存中,所以二级缓存是可以拿到A的事务代理对象的,所以earlySingletonReference不等于null成立,接着再去判断exposedObject是否等于bean,exposedObject是A经过后置处理器返回的bean,而上面我们也看到了,后置处理器并没有对A进行处理然后返回A的代理对象,所以exposedObject就是A的原始对象,所以exposedObject==bean也成立,最终就把earlySingletonReference这个A的事务代理对象赋值给exposedObject,最终返回exposedObject,至此,关于普通的aop的循环依赖过程就结束了
异步代理循环依赖失败的根本原因
上面对于普通的代理对象去进行循环依赖是完全没问题的,那么同样的spring对异步调用的实现也是使用的代理对象去实现的,为什么异步代理循环依赖却失败了呢?假设此时的A加上了@Async注解,那么在第二次实例化A的时候是通过A的对象工厂去创建的,也就是通过调用了AbstractAutoProxyCreator的wrapIfNecessary方法返回并作为B对象的属性A的值,正常来说B此时是可以拿到A的异步代理对象的,但是遗憾的是,异步代理对象并不是通过AbstractAutoProxyCreator这个BeanPostProcessor去创建的,而是通过AsyncAnnotationBeanPostProcessor去创建,而AsyncAnnotationBeanPostProcessor却并不是SmartInstantiationAwareBeanPostProcessor类型的,所以在getEarlyBeanReference方法中是调用不到的,也就是说对象工厂无法返回一个异步代理对象,所以B注入的是一个A的原始对象
当回到第一次实例化A的时候,在执行一系列的BeanPostProcessor的时候,此时会遍历到AsyncAnnotationBeanPostProcessor,进而调用它的后置处理方法返回一个异步代理对象,然后此时重点就来了!回到最后上面最后的一段代码,此时getSingleton返回的对象就是一个A的原始对象,所以earlySingletonReference不等于null,然后exposedObject就是A的异步代理对象,exposedObject == bean并不成立,所以代码跳转到else if,默认的allowRawInjectionDespiteWrapping等于false,所以else if成立,最终就会报BeanCurrentlyInCreationException这个循环依赖的错误
总结
其实主要原因还是因为异步代理实现的方式与其实生成代理的方式不一样,异步代理并没有使用例如aop,事务,缓存切面等这一套的实现方式,像这种实现方式都是去基于AbstractAutoProxyCreator这个BeanPostProcessor去实现的,而异步代理却并不是基于AbstractAutoProxyCreator进行实现的,而是重新使用了另外的一个BeanPostProcessor去进行实现的,为什么不使用基于AbstractAutoProxyCreator的方式就会出现循环依赖的错误呢?因为解决循环依赖的一个关键点在与实例化一个bean的时候提前暴露出一个对象工厂,而这个对象工厂最终返回的只是针对基于AbstractAutoProxyCreator的方式实现的代理对象,所以异步代理用它自己的方式去实现异步代理的话就出现循环依赖的错误