在Service层的方法中,调取了两个其他Service层的方法和自身的Service方法,没有事务保护的情况下(不加注解@Teansactional)方法能够正常运行,且结果正确,但是添加注解后测试方法运行失败,项目也启动失败,抛出如下异常:
主要抛出了两个异常,UnsatisfiedDependencyException即不满足依赖异常,BeanCurrentlyInCreationException直译为当前创建的Bean正在创建中的异常。至此我们大概了解到异常发生原因,实质就是在A中依赖B,B中又依赖A的情况,依赖相互彼此,在创建A的过程中发现有B对象这个成员变量,则转而去创建B对象,而创建B对象时发现B对象中有A对象这个成员变量因此又创建A对象,所以导致无法完成创建。 项目中的类方法关系如下:
要想弄明白事务不生效的原因,我们首先要弄明白Spring中事务的实现原理,而Spring中的声明式事务是使用AOP来实现的。众所周知AOP的实现原理为动态代理,在Spring中使用的两种动态代理,一种是java原生提供的JDK动态代理,另一种是第三方提供的CGLIB动态代理,前者基于接口实现(JDK动态代理只能对实现了接口的类生成代理,而不能针对类),后者基于类实现(CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 因为是继承,所以该类或方法最好不要声明成final),明显后者的适用范围更加广泛,但是原生的JDK动态代理却是速度要快很多,两者各有特色。 原理图↓↓↓
我们要强调,在Spring中原型范围的Bean实例如果发生循环依赖,只有一种下场:抛异常。
而针对单例bean,Spring内部提供了一种有效的提前暴露的机制解决了循环依赖的问题。当然这里仅仅解决的是使用setter方式实现依赖注入的情况,如果是使用构造器依赖注入的情况还是那种下场:抛异常。
抛异常代表,Spring无能力解决此问题,程序出错。
为什么呢?难道Spring不想解决吗?肯定不是,而是无能为力罢了。
我们先简单了解下setter方式实现依赖注入的单例Bean的循环依赖的解决方法:
先介绍下Spring中的那几个缓存池:
singletonObjects :单例缓存池,用于保存创建完成的单例Bean,是Map,凡是创建完毕的Bean实例全部保存在该缓存池中,不存在循环依赖的Bean会直接在创建完之后保存到该缓存中,而存在循环依赖的bean则会在其创建完成后由earlySingletonObjects转移到此缓存中。
singletonFactories:单例工厂缓存池,用于保存提前暴露的ObjectFactory,是Map。
earlySingletonObjects:早期单例缓存池,用于保存尚未创建完成的用于早期暴露的单例Bean,是Map,它与singletonObjects是互斥的,就是不可能同时保存于两者之中,只能择一而存,保存在该缓存池中的是尚未完成创建,而被注入到其他Bean中的Bean实例,可以说该缓存就是一个中间缓存(或者叫过程缓存),只在当将该BeanName对应的原生Bean(处于创建中池)注入到另一个bean实例中后,将其添加到该缓存中,这个缓存中保存的永远是半成品的bean实例,当Bean实例最终完成创建后会从此缓存中移除,转移到singletonObjects缓存中保存。
registeredSingletons:已注册的单例缓存池,用于保存已完成创建的Bean实例的beanName,是Set(此缓存未涉及)。
singletonsCurrentlyInCreation:创建中池,保存处于创建中的单例bean的BeanName,是Set,在这个bean实例开始创建时添加到池中,而来Bean实例创建完成之后从池中移除。
当存在循环依赖的情况时,比如之前的情况:A依赖B,B又依赖A的情况,这种情况下,首先要创建A实例,将其beanName添加到singletonsCurrentlyInCreation池,然后调用A的构造器创建A的原生实例,并将其ObjectFactory添加到singletonFactories缓存中,然后处理依赖注入(B实例),发现B实例不存在且也不在singletonsCurrentlyInCreation池中,表示Bean实例尚未进行创建,那么下一步开始创建B实例,将其beanName添加到singletonsCurrentlyInCreation池,然后调用B的构造器创建A的原生实例,并将其ObjectFactory添加到singletonFactories缓存中,再然后处理依赖注入(A实例),发现A实例尚未创建完成,但在singletonsCurrentlyInCreation池中发现了A实例的beanName,说明A实例正处于创建中,这时表示出现循环依赖,Spring会将singletonFactories缓存中获取对应A的beanName的ObjectFactory中getObject方法返回的Bean实例注入到B中,来完成B实例的创建步骤,同时也会将A的Bean实例添加到earlySingletonObjects缓存中,表示A实例是一个提前暴露的Bean实例,B实例创建完毕之后需要将B的原生实例从singletonFactories缓存中移除,并将完整实例添加到SingletonObjects缓存中(当然earlySingletonObjects中也不能存在),并且将其beanName从singletonsCurrentlyInCreation池中移除(表示B实例完全创建完毕)。然后将B实例注入到A实例中来完成A实例的创建,最后同样将A的原生实例从earlySingletonObjects中移除,完整实例添加到SingletonObjects中,并将A的beanName从创建中池中移除。到此完成A和B两个单例实例的创建。
许多帖子都说循环依赖的问题不能解决,其实错了,准确的说应该是饿汉加载且单例模式下无法解决,提供三种解决方法:
1、使用 @Lazy 和@Autowired注解: 例如:类A、B中相互添加了对方的依赖,只需在一方自动装配时添加@Lazy注解即可如下(该注解的包位置: import org.springframework.context.annotation.Lazy)
2、使用基于 Setter 的注入 例如:
@Component public classDealerService { private AsyncUpdate a; public void setA(AsyncUpdate a) { this.a = a; } } @Component public classAsyncUpdate { private DealerService ds; public void setDs(DealerService ds) { this.ds = ds; } }3、使用@Autowired注解防止循环注入,如
@Component public class DealerService { @Autowired private AsyncUpdate a; } @Component public class AsyncUpdate { private DealerService ds; @Autowired public void foo(DealerService ds) { this.ds= ds; } }@Autowired注解对方法的使用说明:@Autowired 对方法或构造函数进行标注时,表示如果构造函数有两个入参,分别是 bean1 和bean2,@Autowired 将分别寻找和它们类型匹配的 Bean,将它们作为 baanService (Bean1 bean1 ,Bean2 bean2) 的入参来创建baanService Bean。
特此十分感谢两篇博主对相关内容的分享: 分析问题帖子:戳我 解决问题帖子:戳我
余生还长,切勿惆怅
