【spring】spring学习系列之七:循环依赖

系列文章目录


前言

循环依赖是指两个或多个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;
	}

最终解决循环依赖的整体流程如下:
在这里插入图片描述

三、解决循环依赖的其他办法

上面是通过三级缓存解决循环依赖,还有其他方式,比如懒加载

  1. 懒加载:
@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();来获取
这些就不展开了

总结

  1. spring是怎么发现循环依赖的:spring实例化bean是单线程的,获取依赖的bean时发现依赖的bean正在创建中,那么就是循环依赖
  2. 循环依赖怎么解决:通过三级缓存机制实现
  3. 为什么使用三级缓存:三级缓存缓存的是lambda表达式,有AOP扩展点,让AOP提前,从而是的注入的bean与单例池的bean保持相同
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值