系列文章目录
前言
循环依赖是指两个或多个Bean相互依赖,形成闭环的情况。例如:Bean A依赖Bean B,而Bean B又依赖Bean A。
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
一、spring怎么知道发生了循环依赖
上一篇讲实例化bean时,是从createBean方法开始的,createBean方法是被getSingleton方法包起来的,spring怎么知道发生了循环依赖的原因就在这里了
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
先看下getSingleton的代码
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
//把beanName放进正在创建的集合singletonsCurrentlyInCreation
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
//执行createBean方法
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
//把beanName移出正在创建的集合singletonsCurrentlyInCreation
afterSingletonCreation(beanName);
}
if (newSingleton) {
//添加到单例池
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
1)把beanName放进正在创建的集合singletonsCurrentlyInCreation
2)执行createBean方法
3)把beanName放进正在创建的集合singletonsCurrentlyInCreation
4)添加到单例池
getSingleton方法对createBean方法的包装,使得在创建bean之前和之后能够做一些处理。也就是放入和移出集合singletonsCurrentlyInCreation,singletonsCurrentlyInCreation就是正在创建的beanName的集合
那么我们可以推断,要解决依赖注入时的循环依赖问题,在注入的时候就要判断出依赖的对象是否正在创建中。可能有人会问,正在创建中就能说明正在循环依赖吗?没错,因为spring启动流程中,实例化bean是单线程执行的,所以要依赖的bean正在创建中就能说明产生了循环依赖!
上一篇讲自动注入填充属性最终是调用了beanFactory.getBean(beanName, args)来获取依赖的对象
,而getBean方法首先要从单例池拿,也就是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;
}
果不其然,getSingleton方法中就是判断了依赖的对象是否正在创建中
现在知道了怎么判断出循环依赖,那怎么解决循环依赖呢?
二、解决循环依赖
还是从刚刚getSingleton方法看起
1)先从一级缓存中拿,拿到了就返回
2)拿不到就判断是否正在创建中,如果不是就返回null,走createBean流程。如果是,就从二级缓存中拿
3)二级缓存拿不到再从三级缓存中拿,三级缓存存放的是lambda表达式,在lambda表达式有AOP的扩展点
4)从三级缓存拿到对象后,放入二级缓存,再删除三级缓存
这里就开始引出循环依赖的经典问题了,为啥要用三级缓存,二级缓存直接存放早期对象不就可以解决循环依赖了吗?
还记得上一篇中讲初始化后的方法吗,初始后方法中有AOP的扩展点,生成代理对象,对实例进行增强,最后放到单例池的是代理对象。那么如果用二级缓存直接存放早期对象注入,那么注入的对象就没有增强的功能,那不就有问题了吗?
所以为了解决这个问题,就有了三级缓存,三级缓存存放的是lambda表达式,在lambda表达式有AOP的扩展点,把AOP代理提前了。等到初始化后方法中代理时判断是否代理过了,代理过就不在代理了。这样最终放入单例池的对象和依赖注入的对象就是同一个对象了
三级缓存的AOP扩展点:
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;
}
最终解决循环依赖的整体流程如下:
三、解决循环依赖的其他办法
上面是通过三级缓存解决循环依赖,还有其他方式,比如懒加载
- 懒加载:
@Service
public class ServiceA {
@Autowired
@Lazy
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
上一篇讲过,自动注入时,如果注入的bean上有@Lazy注解会生成一个代理对象,注入这个代理对象,等真正使用bean的时候,再通过代理对象去容器里获取真正的对象。
注意:这里虽然说注入的是代理对象,但是执行的时候是真正的对象,不会产生上面说的注入早期对象的问题。
四、循环依赖原理的应用
我们有时候在进行AOP代理的时候,会发生代理失效的问题,比如对MyService 的
所有方法都进行AOP代理,如果在methodB中调用methodA方法,调用的是真实对象的方法(其实就是this.methodA方法),AOP 切面会失效,如下:
@Service
public class MyService {
public void methodA() {
System.out.println("Doing something...");
}
public void methodB() {
methodA(); // 这里调用的是真实对象的方法,AOP 切面会失效
}
}
那怎么解决呢?可以把自己注入给自己,前面说过注入的时候是从三级缓存拿的,在三级缓存中提前进行了代理,注入的就是代理对象了
@Service
public class MyService {
@Autowired
private MyService self; // 这里注入的是代理对象
public void methodA() {
System.out.println("Doing something...");
}
public void methodB() {
self.methodA(); // 这里调用的是代理对象的方法,AOP 切面会生效
}
}
当然还有其他的方式解决,只要能拿到代理对象即可,比如
1)通过ApplicationContext容器获取
2)通过@EnableAspectJAutoProxy(exposeProxy = true)注解后,调用MyService proxy = (MyService) AopContext.currentProxy();来获取
这些就不展开了
总结
- spring是怎么发现循环依赖的:spring实例化bean是单线程的,获取依赖的bean时发现依赖的bean正在创建中,那么就是循环依赖
- 循环依赖怎么解决:通过三级缓存机制实现
- 为什么使用三级缓存:三级缓存缓存的是lambda表达式,有AOP扩展点,让AOP提前,从而是的注入的bean与单例池的bean保持相同