目录
0. 序!
一个@Async注解引发了项目报错,伪代码如下:
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
@Async
public void test1() {
}
}
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
实际报错:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmlCustomscantimesServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmlQrcodeServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmlFlowingServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'commonServiceImpl': Bean with name 'commonServiceImpl' has been injected into other beans [jmlMoneyflowingServiceImpl] 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.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:895)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
at com.shangjietech.jmlseventeen.JmlseventeenApplication.main(JmlseventeenApplication.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:51)
at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:52)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmlQrcodeServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmlFlowingServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'commonServiceImpl': Bean with name 'commonServiceImpl' has been injected into other beans [jmlMoneyflowingServiceImpl] 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.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:521)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:497)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:650)
at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:239)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:318)
... 25 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmlFlowingServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'commonServiceImpl': Bean with name 'commonServiceImpl' has been injected into other beans [jmlMoneyflowingServiceImpl] 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.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:521)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:497)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:650)
at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:239)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:318)
... 41 common frames omitted
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'commonServiceImpl': Bean with name 'commonServiceImpl' has been injected into other beans [jmlMoneyflowingServiceImpl] 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:624)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:521)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:497)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:650)
at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:239)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:318)
... 57 common frames omitted
核心报错其实就一句:
Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'commonServiceImpl': Bean with name 'commonServiceImpl' has been injected into other beans [jmlMoneyflowingServiceImpl] 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.
百度翻译:
org.springframework.beans.factory.Beancourrentlyincreationexception: 创建名为“commonServiceImpl”的bean时出错:名为“commonServiceImpl”的Bean已作为循环引用的一部分在其原始版本中注入到其他Bean[jmlMoneyflowingServiceImpl]中,但最终被包装。这意味着所述其他bean不使用该bean的最终版本。这通常是过于急切的类型匹配的结果,例如,考虑在关闭“allowEagerInit”标志的情况下使用“getBeanNamesForType”。
大白话:
在创建bean名称commonServiceImpl时,发现commonServiceImpl在循环依赖中已经以原始bean实例引用注入到其他bean(jmlMoneyflowingServiceImpl)中,但是最终bean实例commonServiceImpl被进行包装,导致前后不一致,出现了多个版本实例。
但是,如上伪代码,将@Async注解去掉,项目启动则不会报错,即有@Async注解则出现循环依赖,无@Async注解则不出现循环依赖!
由此,我才认识到什么是循环依赖,不得不说spring实在是封装太深了,果然面试经不代表项目经验…
1. 循环依赖到底是什么?
何谓循环依赖,其实就是形成一个闭环依赖,关键在于闭环,只要成为闭环都是循环,仔细想想,死锁也是因为闭环。闭环!!!闭环!!!
-
自依赖
-
相互依赖
-
多级依赖
说白是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了构成一个环形调用。但是请注意,spring是可以自己解决部分循环依赖的,这也是为什么@Async注解去掉伪代码则可以正常启动的原因。
2. 检测是否存在循环依赖?
Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。
3. spring怎么处理循环依赖?
spring已经默默的帮我解决了循环依赖,只要在spring的规范下使用,即使出现了循环依赖,项目也是可以正常启动的,原因就是spring的apo和ioc
spring内部有三级缓存:
- singletonObjects:一级缓存,用于保存实例化、注入、初始化完成的bean实例。
其作用是保存成熟Bean - earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例。
其作用是保存原生的早期Bean,不成熟的Bean - singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。
其作用是保存代理的Bean
spring三级缓存有四大规则:
- 无论是对象实例化还是依赖注入,都是在缓存中逐级寻找(先找一级,再找二级,再中三级,三级没有则初步创建)。
- 实例在多级缓存中是移动而非复制,从无到3,从3到2,从2到1,或者从3直接到1。
- 依赖注入时如果是被动加载,则不会加入二级缓存,即被动加载时会一步到位直接从三级缓存移动到一级缓存。
- spring中的ioc注入的实例均为单例,发现不为单例则报错。
4. 三级缓存的源码分析
Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或则属性是可以延后设置的(但是构造器必须是在获取引用之前)。
Spring的单例对象的初始化主要分为三步:
-
createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
-
populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
-
initializeBean:调用spring xml中的init 方法。
从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二部。也就是构造器循环依赖和field循环依赖。
那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。
首先我们看源码,三级缓存主要指:
//一级
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
//二级
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
//三级
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/**
* 提前暴露
* sprig特意将刚刚创建好的bean实例传入到getEarlyBeanReference匿名内中,因为在获取早期bean的时候,不会涉及到AOP,则返回原本创建好的实例
* 但是这里的bean实例还未填充属性与初始化,所以是一个不完成的bean
* 这里就是解决循环依赖的一个点,在bean在实例化时,提前将还未完全实例化好的早期bean通过缓存向外界暴露
* 这样其他bean在实例化需要依赖时,就可以在缓存中获取到早期的bean实例。
*/
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
这三级缓存分别指:
- singletonFactories : 单例对象工厂的cache
- earlySingletonObjects :提前暴光的单例对象的Cache
- singletonObjects:单例对象的cache
我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就就是:
// AbstractBeanFactory.doGetBean
// 尝试从缓存中获取对象A
Object sharedInstance = getSingleton(beanName);
// DefaultSingletonBeanRegistry.getSingleton
public Object getSingleton(String beanName) {
// 注意第二个参数为true
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1:从单列缓存中换取单列bean
Object singletonObject = this.singletonObjects.get(beanName);
// 2:如果bean还未完全实例化,并且是处于正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 3:尝试从二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
// 4:依然为null,且允许创建早期引用,allowEarlyReference传进来的值为true
if (singletonObject == null && allowEarlyReference) {
// 5:从三级缓存中获取(从单例工厂中获取早期单例的工厂对象)
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 6:如果早期的单例工厂对象存在,就通过工厂创建早期的bean
// 这里是重点:会调用前面的getEarlyBeanReference来获取A对象实例
singletonObject = singletonFactory.getObject();
// 7:然后将bean的半成品单列对象,放入到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
// 8:从三级缓存中删除
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
/**上面的代码需要解释两个参数
* isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
* allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
*/
分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则从singletonFactories中移除,并放入earlySingletonObjects中。即从三级缓存移动到二级缓存。
因此从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义如下:
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
这个接口在下面被引用
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
这里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。
知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
5. 循环依赖下的代理对象创建过程
我们都知道Spring AOP、事务等都是通过代理对象来实现的,而事务的代理对象是由自动代理创建器来自动完成的。也就是说Spring最终给我们放进容器里面的是一个代理对象,而非原始对象。
这里我们结合循环依赖,再分析一下AOP代理对象的创建过程和最终放进容器内的动作,看如下代码:
@Service
public class MyServiceImpl implements MyService {
@Autowired
private MyService myService;
@Transactional
@Override
public Object hello(Integer id) {
return "service hello";
}
}
此Service类使用到了事务,所以最终会生成一个JDK动态代理对象Proxy。刚好它又存在自己引用自己的循环依赖的情况。跟进到Spring创建Bean的源码部分,来看doCreateBean()方法:
protected Object doCreateBean( ... ){
...
// 如果允许循环依赖,此处会添加一个ObjectFactory到三级缓存里面,以备创建对象并且提前暴露引用
// 此处Tips:getEarlyBeanReference是后置处理器SmartInstantiationAwareBeanPostProcessor的一个方法,
// 主要是保证自己被循环依赖的时候,即使被别的Bean @Autowire进去的也是代理对象
// AOP自动代理创建器此方法里会创建的代理对象
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() &&
this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) { // 需要提前暴露(支持循环依赖),注册一个ObjectFactory到三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 如果发现自己被循环依赖,会执行上面的getEarlyBeanReference()方法,从而创建一个代理对象从三级缓存转移到二级缓存里
// 注意此时候对象还在二级缓存里,并没有在一级缓存。并且此时可以知道exposedObject仍旧是原始对象 populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
// 经过这两大步后,exposedObject还是原始对象
// 注意:此处是以事务的AOP为例
// 因为事务的AOP自动代理创建器在getEarlyBeanReference()创建代理后,
// initializeBean() 就不会再重复创建了,二选一,下面会有详细描述)
...
// 循环依赖校验(非常重要)
if (earlySingletonExposure) {
// 前面讲到因为自己被循环依赖了,所以此时候代理对象还存放在二级缓存中
// 因此,此处getSingleton(),就会把代理对象拿出来
// 然后赋值给exposedObject对象并返回,最终被addSingleton()添加进一级缓存中
// 这样就保证了我们容器里缓存的对象实际上是代理对象,而非原始对象
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 这个判断不可少(因为initializeBean()方法中给exposedObject对象重新赋过值,否则就是是两个不同的对象实例)
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
}
...
}
}
以上代码分析的是代理对象有自己存在循环依赖的情况,Spring用三级缓存很巧妙的进行解决了这个问题。
6. 非循环依赖下的代理对象创建过程
如果自己并不存在循环依赖的情况,Spring的处理过程就稍微不同,继续跟进源码:
protected Object doCreateBean( ... ) {
...
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
...
// 此处注意,因为没有循环引用,所以上面getEarlyBeanReference()方法不会执行
// 也就是说此时二级缓存里并不会存在
populateBean(beanName, mbd, instanceWrapper);
// 重点在此
//AnnotationAwareAspectJAutoProxyCreator自动代理创建器此处的postProcessAfterInitialization()方法里,会给创建一个代理对象返回
// 所以此部分执行完成后,exposedObject() 容器中缓存的已经是代理对象,不再是原始对象
// 此时二级缓存里依旧无它,更别提一级缓存了
exposedObject = initializeBean(beanName, exposedObject, mbd);
...
// 循环依赖校验
if (earlySingletonExposure) {
// 前面讲到一级、二级缓存里都没有缓存,然后这里传参数是false,表示不从三级缓存中取值
// 因此,此时earlySingletonReference = null ,并直接返回
// 然后执行addSingleton()方法,由此可知,容器里最终存在的也还是代理对象
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
}
...
}
根据以上代码分析可知,只要用到代理,没有被循环引用的,最终存在Spring容器里缓存的仍旧是代理对象。如果我们关闭Spring容器的循环依赖,也就是把allowCircularReferences设值为false,那么会不会出现问题呢?先关闭循环依赖开关。
// 它用于关闭循环引用(关闭后只要有循环引用现象将报错)
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(false);
}
}
关闭循环依赖后,上面代码中存在A、B循环依赖的情况,运行程序会出现如下异常:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
此处异常类型也是BeanCurrentlyInCreationException异常,但报错位置在DefaultSingletonBeanRegistry.beforeSingletonCreation
我们来分析一下,在实例化A后给其属性赋值时,Spring会去实例化B。B实例化完成后会继续给B属性赋值,由于我们关闭了循环依赖,所以不存在提前暴露引用。因此B无法直接拿到A的引用地址,只能又去创建A的实例。而此时我们知道A其实已经正在创建中了,不能再创建了。所有就出现了异常。对照演示代码,来分析一下程序运行过程:
@Service
public class MyServiceImpl implements MyService {
// 因为关闭了循环依赖,所以此处不能再依赖自己
// 但是MyService需要创建AOP代理对象
//@Autowired
//private MyService myService;
@Transactional
@Override
public Object hello(Integer id) {
return "service hello";
}
}
其大致运行步骤如下:
protected Object doCreateBean( ... ) {
// earlySingletonExposure = false 也就是Bean都不会提前暴露引用,因此不能被循环依赖
boolean earlySingletonExposure = (mbd.isSingleton() &&
this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
...
populateBean(beanName, mbd, instanceWrapper);
// 若是开启事务,此处会为原生Bean创建代理对象
exposedObject = initializeBean(beanName, exposedObject, mbd);
if (earlySingletonExposure) {
...
// 因为上面没有提前暴露代理对象,所以上面的代理对象exposedObject直接返回。
}
}
由上面代码可知,即使关闭循环依赖开关,最终缓存到容器中的对象仍旧是代理对象,显然@Autowired给属性赋值的也一定是代理对象。
最后,以AbstractAutoProxyCreator为例看看自动代理创建器实现循环依赖代理对象的细节。
AbstractAutoProxyCreator是抽象类,它的三大实现子类InfrastructureAdvisorAutoProxyCreator、AspectJAwareAdvisorAutoProxyCreator、AnnotationAwareAspectJAutoProxyCreator小伙伴们应该比较熟悉,该抽象类实现了创建代理的动作:
// 该类实现了SmartInstantiationAwareBeanPostProcessor接口 ,通过getEarlyBeanReference()方法解决循环引用问题
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
...
// 下面两个方法是自动代理创建器创建代理对象的唯二的两个节点:
// 提前暴露代理对象的引用,在postProcessAfterInitialization之前执行
// 创建好后放进缓存earlyProxyReferences中,注意此处value是原始Bean
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
// 因为它会在getEarlyBeanReference之后执行,这个方法最重要的是下面的逻辑判断
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 下面的remove()方法返回被移除的value,也就是原始Bean
// 判断如果存在循环引用,也就是执行了上面的getEarlyBeanReference()方法,
// 此时remove() 返回值肯定是原始对象
// 若没有被循环引用,getEarlyBeanReference()不执行
// 所以remove() 方法返回null,此时进入if执行逻辑,调用创建代理对象方法
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
...
}
根据以上分析可得知,自动代理创建器它保证了代理对象只会被创建一次,而且支持循环依赖的自动注入的依旧是代理对象。由上面分析得出结论,在Spring容器中,不论是否存在循环依赖的情况,甚至关闭Spring容器的循环依赖功能,它对Spring AOP代理的创建流程有影响,但对结果是无影响的。也就是说Spring很好地屏蔽了容器中对象的创建细节,让使用者完全无感知。
7. 循环依赖的主要场景?
7.1 单例的setter注入
这种注入方式应该是spring用的最多的,代码如下:
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
一张图展示,spring是如何解决单例setter循环依赖的:
感觉相互依赖这种场景中其实第二级缓存作用不大。那么问题来了,为什么要用第二级缓存呢?是的,其目的就是因为多级相互依赖。
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
@Autowired
private TestService3 testService3;
public void test1() {
}
}
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
@Service
publicclass TestService3 {
@Autowired
private TestService1 testService1;
public void test3() {
}
}
按照上图的流程可以把TestService1注入到TestService2,并且TestService1的实例是从第三级缓存中获取的。
假设不用第二级缓存,TestService1注入到TestService3又需要从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是ObjectFactory对象。说白了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象每次可能都不一样的。
这显然不行,为了解决这个问题,spring引入的第二级缓存。上面图1其实TestService1对象的实例已经被添加到第二级缓存中了,而在TestService1注入到TestService3时,只用从第二级缓存中获取该对象即可。
还有个问题,第三级缓存中为什么要添加ObjectFactory对象,直接保存实例对象不行吗?
不行,因为假如你想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。
针对这种场景spring是怎么做的呢?
答案就在AbstractAutowireCapableBeanFactory类doCreateBean方法的这段代码中:
它定义了一个匿名内部类,通过getEarlyBeanReference方法获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。
7.2 多例的setter注入
这种注入方法偶然会有,特别是在多线程的场景下,具体代码如下:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
很多人说这种情况spring容器启动会报错,其实是不对的,我非常负责任的告诉你程序能够正常启动。
为什么呢?
其实在AbstractApplicationContext类的refresh方法中告诉了我们答案,它会调用finishBeanFactoryInitialization方法,该方法的作用是为了spring容器启动的时候提前初始化一些bean。该方法的内部又调用了preInstantiateSingletons方法
AbstractAutowireCapableBeanFactory类doCreateBean方法中标红的地方明显能够看出:非抽象、单例 并且非懒加载的类才能被提前初始bean。
而多例即SCOPE_PROTOTYPE类型的类,非单例,不会被提前初始化bean,所以程序能够正常启动。
如何让他提前初始化bean呢?
只需要再定义一个单例的类,在它里面注入TestService1
@Service
publicclass TestService3 {
@Autowired
private TestService1 testService1;
}
重新启动,果然出现了循环依赖:
Requested bean is currently in creation: Is there an unresolvable circular reference?
注意:这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。
7.3 构造器注入
这种注入方式是spring4.x以上的版本中官方推荐的方式(但是,实际中真的很少用,因为这种循环依赖spring无法解决),具体如下代码:
@Service
publicclass TestService1 {
public TestService1(TestService2 testService2) {
}
}
@Service
publicclass TestService2 {
public TestService2(TestService1 testService1) {
}
}
运行结果:
Requested bean is currently in creation: Is there an unresolvable circular reference?
出现了循环依赖,为什么呢?
从图中的流程看出构造器注入只是添加了三级缓存,并没有使用缓存,所以也无法解决循环依赖问题。
7.4 单例的代理对象setter注入
这种注入方式其实也比较常用,比如平时使用:@Async注解的场景,会通过AOP自动生成代理对象。
代码如下:
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
@Async
public void test1() {
}
}
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
如上代码程序启动会报错,出现了循环依赖:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] 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初始化完成之后,后面还有一步去检查:第二级缓存 和 原始对象 是否相等。由于它对前面流程来说无关紧要,所以前面的流程图中省略了,但是在这里是关键点,我们重点说说:
上面的情况正好是走到这段代码,发现第二级缓存 和 原始对象不相等,所以抛出了循环依赖的异常。
小tip:如果这时候把TestService1改个名字,改成:TestService6,其他的都不变。
@Service
publicclass TestService6 {
@Autowired
private TestService2 testService2;
@Async
public void test1() {
}
}
再重新启动一下程序,神奇般的好了。
what? 这又是为什么?
这就要从spring的bean加载顺序说起了,默认情况下,spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载。所以TestService1比TestService2先加载,而改了文件名称之后,TestService2比TestService6先加载。
为什么TestService2比TestService6先加载就没问题呢?
答案在下面这张图中:
这种情况testService6中其实第二级缓存是空的,不需要跟原始对象判断,所以不会抛出循环依赖。----重点就是被动加载时不会加入二级缓存,因为可以一步到位。
7.5 DependsOn循环依赖
还有一种有些特殊的场景,比如我们需要在实例化Bean A之前,先实例化Bean B,这个时候就可以使用@DependsOn注解。
@DependsOn(value = "testService2")
@Service
publicclass TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@DependsOn(value = "testService1")
@Service
publicclass TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
程序启动之后,执行结果:
Circular depends-on relationship between 'testService2' and 'testService1'
这个例子中本来如果TestService1和TestService2都没有加@DependsOn注解是没问题的,反而加了这个注解会出现循环依赖问题。
这又是为什么?
答案在AbstractBeanFactory类的doGetBean方法的这段代码中:它会检查dependsOn的实例有没有循环依赖,如果有循环依赖则抛异常。
8. 出现循环依赖如何解决?
项目中如果出现循环依赖问题,说明是spring默认无法解决的循环依赖,要看项目的打印日志,属于哪种循环依赖。目前包含下面几种情况:
8.1 生成代理对象产生的循环依赖
这类循环依赖问题解决方法很多,主要有:
- 使用@Lazy注解,延迟加载
- 使用@DependsOn注解,指定加载先后关系
- 修改文件名称,改变循环依赖类的加载顺序
8.2 使用@DependsOn产生的循环依赖
这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题。
8.3 多例循环依赖
这类循环依赖问题可以通过把bean改成单例的解决。
8.4 构造器循环依赖
这类循环依赖问题可以通过使用@Lazy注解解决。
当然最好的解决循环依赖问题最佳方案是从代码设计上规避,但是复杂的系统中有可能没法避免。
9. @Async注解与循环依赖
@Service
public class B {
@Autowired
private A a;
}
@Service
public class A {
@Autowired
private B b;
@Async
public void test () {}
}
以上代码中,加了异步注解便会出现循环依赖,不加则不会,一下便是详细解释
-
创建A对象时,在填充属性前,会将A对象提前暴露在三级缓存
// 三级缓存,存放单例早期的bean工厂对象 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** * 提前暴露 * sprig特意将刚刚创建好的bean实例传入到getEarlyBeanReference匿名内中,因为在获取早期bean的时候,不会涉及到AOP,则返回原本创建好的实例 * 但是这里的bean实例还未填充属性与初始化,所以是一个不完成的bean * 这里就是解决循环依赖的一个点,在bean在实例化时,提前将还未完全实例化好的早期bean通过缓存向外界暴露 * 这样其他bean在实例化需要依赖时,就可以在缓存中获取到早期的bean实例。 */ addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
-
填充对象A属性时,发现依赖了B对象,则开始创建B对象
-
同理,在创建B对象时,发现需要依赖A对象,此时则会尝试从缓存中获取对象A
// AbstractBeanFactory.doGetBean // 尝试从缓存中获取对象A Object sharedInstance = getSingleton(beanName); // DefaultSingletonBeanRegistry.getSingleton public Object getSingleton(String beanName) { // 注意第二个参数为true return getSingleton(beanName, true); } protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 1:从单列缓存中换取单列bean Object singletonObject = this.singletonObjects.get(beanName); // 2:如果bean还未完全实例化,并且是处于正在创建中 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 3:尝试从二级缓存中获取 singletonObject = this.earlySingletonObjects.get(beanName); // 4:依然为null,且允许创建早期引用,allowEarlyReference传进来的值为true if (singletonObject == null && allowEarlyReference) { // 5:从三级缓存中获取(从单例工厂中获取早期单例的工厂对象) ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 6:如果早期的单例工厂对象存在,就通过工厂创建早期的bean // 这里是重点:会调用前面的getEarlyBeanReference来获取A对象实例 singletonObject = singletonFactory.getObject(); // 7:然后将bean的半成品单列对象,放入到二级缓存 this.earlySingletonObjects.put(beanName, singletonObject); // 8:从三级缓存中删除 this.singletonFactories.remove(beanName); } } } } return singletonObject; }
-
然后将A注入到对象B中
-
对象B完成创建、初始化后,继续A的流程,将对象B注入到对象A属性中。
-
对象A进行初始化
至此对象A、B均已完成。 底层的一个大致原理是这样。但我们还是没说明为什么加了@Async注解会报错的问题,前面说过在B对象填充属性时,会去缓存中获取A对象,会调用addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));中getEarlyBeanReference匿名方法。 跟踪源码发现会进入到AbstractAutowireCapableBeanFactory.getEarlyBeanReference中 。 protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { // 判断是否有实现了SmartInstantiationAwareBeanPostProcessor的BeanPostProcessor // 默认是没有的 if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; // 默认直接返回我们创建来的bean // 这里同样是Spring留给我们的一个扩展点的,希望我们自定义实现接口SmartInstantiationAwareBeanPostProcessor // 然后重写方法getEarlyBeanReference,自定义早期单例的bean应该需要哪些东西,好让其他bean实例化时,得到符合需求的早期单例bean。 exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; } 我们断点发现getBeanPostProcessors()有以下20个BeanPostProcessors,其中包括了AsyncAnnotationBeanPostProcessors
此时循环到AsyncAnnotationBeanPostProcessors后置处理器时,从图中我们发现
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) 条件根本不成立,所以就不会执行AsyncAnnotationBeanPostProcessors后置拦截处理器,那么AsyncAnnotationBeanPostProcessors在哪里会进行执行???
遍历完所有的后置处理器后,我们发现返回的对象实例是原始传进来的对象实例,没有进行任何的包装对象实例。
此时对象B填充属性A时,已经在缓存中获取到了对象A,并且对象A会添加进二级缓存中去(前面提到过)。此时会继续B的初始化等操作。 我们发现上面这些步骤完全没有问题,那问题来了,到底是哪一步出现了问题??? 我们可以猜想到,此时A还没进行初始化,那么问题是不是会在初始化环节,继续跟踪源码。// AbstractAutowireCapableBeanFactory.doCreateBean // 初始化bean exposedObject = initializeBean(beanName, exposedObject, mbd); protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { invokeAwareMethods(beanName, bean); return null; }, getAccessControlContext()); } else { // spring在实例化时,最终会调用感知接口中的方法,将spring容器内相应的组件注入到bean的实例中。 invokeAwareMethods(beanName, bean); } // 上面的步骤是不会改变bean对象的,所以可以直接跳过 Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { // 1)bean初始化前,通过bean后置处理(before)调整下bean的实例 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { // 执行bean的初始化 invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException( (mbd != null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex); } if (mbd == null || !mbd.isSynthetic()) { // 2)bean初始化后,执行bean的后置处理器(after) wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; } 通过源码我们发现,在 1、2处会对bean实例进行一个扩展。我们通过debug跟踪源码发现在代码1处并没有对原始bean实例进行包装。那么必然是在after中进行包装了。
进入after后置处理器瞧瞧,我们发现又是执行各种后置处理器// AbstractAutowireCapableBeanFactory public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; } 我们还是主要看AsyncAnnotationBeanPostProcessors。
跟进AsyncAnnotationBeanPostProcessors执行源码 // AbstractAdvisingBeanPostProcessor public Object postProcessAfterInitialization(Object bean, String beanName) { // 判断advisor通知器是否不为空、判断bean是不是实现了AopInfrastructureBean if (this.advisor != null && !(bean instanceof AopInfrastructureBean)) { // 判断bean是不是实现了Advised,我们这里bean并不是切面的形式,所以条件不成立 if (bean instanceof Advised) { Advised advised = (Advised)bean; if (!advised.isFrozen() && this.isEligible(AopUtils.getTargetClass(bean))) { if (this.beforeExistingAdvisors) { advised.addAdvisor(0, this.advisor); } else { advised.addAdvisor(this.advisor); } return bean; } } // 此时会进入到这里,注意这里面源码进行了取反 // 这里其实就是判断一下bean是否能匹配到对应类型的advisor if (this.isEligible(bean, beanName)) { ProxyFactory proxyFactory = this.prepareProxyFactory(bean, beanName); if (!proxyFactory.isProxyTargetClass()) { this.evaluateProxyInterfaces(bean.getClass(), proxyFactory); } proxyFactory.addAdvisor(this.advisor); this.customizeProxyFactory(proxyFactory); // 重点:创建了一个代理对象并返回。 return proxyFactory.getProxy(this.getProxyClassLoader()); } else { return bean; } } else { return bean; } } // AbstractBeanFactoryAwareAdvisingPostProcessor protected boolean isEligible(Object bean, String beanName) { // 重点关注一下super.isEligible(bean, beanName) return !AutoProxyUtils.isOriginalInstance(beanName, bean.getClass()) && super.isEligible(bean, beanName); } // protected boolean isEligible(Object bean, String beanName) { return this.isEligible(bean.getClass()); } protected boolean isEligible(Class<?> targetClass) { Boolean eligible = (Boolean)this.eligibleBeans.get(targetClass); if (eligible != null) { return eligible; } else if (this.advisor == null) { return false; } else { // 这里会将返回true eligible = AopUtils.canApply(this.advisor, targetClass); this.eligibleBeans.put(targetClass, eligible); return eligible; } } 我们通过源码发现AsyncAnnotationBeanPostProcessors返回的是一个cglib的代理对象bean
因此初始化后,返回的是一个cglib增强的bean对象
此时我们已经发现了注入到B对象的A属性是原始的实例,但是A初始化后已经是一个包装过后的实例了(cglib),因为spring默认是单例,这肯定会出问题。初始化完后,发现spring还会进行一次对比,源码如下:// AbstractAutowireCapableBeanFactory.doCreateBean if (earlySingletonExposure) { // 依然从缓存中获取,注意这里第二个参数是false,也就是说只能从一级缓存、二级缓存中获取 // 因为此时还未放入一级缓存,所以肯定是没有的,只能从二级缓存中获取 Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // 这里会进行一个比较,看二级缓存中的bean实例是否与初始化后的bean实例相等,此时发现并不相等 if (exposedObject == bean) { exposedObject = earlySingletonReference; } // 接下来就会判断这个bean是否有其他bean进行依赖,如果有则说明注入到其他bean的依赖不是最终包装过后的bean 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."); } } } } 至此,问题的原因我们已经找到。那么如何解决了??? 如何解决 通过懒加载的形式。代码如下: @Service public class B { // 对实例A进行一个懒加载 @Lazy @Autowired private A a; }
10. @Async与@Transactional对循环依赖的影响
今天写项目时连用了@Async和@Transactional,产生了循环依赖。代码如下:
public class JmlRiskRuleServiceImpl extends ServiceImpl<JmlRiskRuleMapper, JmlRiskRule> implements IJmlRiskRuleService {
private static final Logger logger = LoggerFactory.getLogger(JmlRiskRuleServiceImpl.class);
@Resource
private IJmlRiskRuleService iJmlRiskRuleService;
@Override
@Async
@Transactional
public void scanRiskHandle(HttpServletRequest request, JmlMpWechatuser wxUser, String projectCode, String qrcode) {
try {
...
iJmlRiskRuleService.save();
}catch (Exception e){
logger.info("扫码风控异常", e);
}
}
}
启动项目时报错:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'jmlRiskRuleServiceImpl': Bean with name 'jmlRiskRuleServiceImpl' has been injected into other beans [jmlRiskRuleServiceImpl] 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.
一开始以为是 @Async与@Transactional连用导致了循环,但是其实不是,这两个注解根本就不会有冲突。
@Async的作用是开启一个新线程,主线程(调用线程)与从线程(被调用线程)之间是完全隔离的,注意,是完全隔离,线程隔离!
@Transactional的作用是mysql的事务控制。
springboot中的各种功能注解都是用aop进行实现的,核心都是靠着关键的MethodInterceptor实现,这两个注解也不例外,@Async会给对应bean代理对象中放入一个AnnotationAsyncExecutionInterceptor拦截器,而@Transactional会给对应bean的代理对象中放入一个TransactionInterceptor拦截器。并且拦截器是有优先级的,无论什么时候@Async注解提供的拦截器优先级都是最高,因为它的作用是开启一个新线程,并且是完全隔离的线程。
所以以上代码之所以产生循环依赖,完全是因为自依赖的情况下使用了@Async,跟@Transactional完全无关, 一般来说使用@Async时往往会产生springboot无法自解决的循环依赖,因为线程的产生比较复杂。
以上其实仍然是@Async对循环依赖的影响。
在此再说一下,@Async与@Transactional连用产生的影响。
@Async注解的作用是开启一个新线程,主线程与异步线程之间完全隔离,完全隔离的意义是完全不影响,事务@Transactional只限制在线程之内。
@Async
@Transactional
A
@Async
@Transactional
B
调用时 A.B --》 此时,a和b完全是两个线程,自然也是两个事务,此时不会遵从Transactional的定义合并事务。因为本质是两个线程。
@Async注解的方法上,再标注@Transactional注解,事务依旧是生效的,但生效范围只是本线程。
- 是否是一个事务,不能只看注解,还要看是否在一个线程,也就是看有没有调用方法中有没有@Async注解修饰
- 不同线程之间的事务完全隔离,换句话说,事务局限于线程之内
- 异步方法内仍是可以调用异步方法的,一个@Async注解便会开启一个新线程。