大家都知道Spring 通过三级缓存来解决循环依赖问题,但是我今天写出来个循环依赖,直接报错,难受啊,三级缓存并没有被关闭,本篇文章我们就来深入分析一下三级缓存,以及为啥我的项目启动不了,罪魁祸首就是@Async。
1. 什么是循环依赖?
上代码!
@Service
class ServiceA {
@Autowired
ServiceB serviceB;
public void test() {
}
}
@Service
class ServiceB {
@Autowired
ServiceA serviceA;
public void test() {
}
}
简单的说就是 你中有我,我中有你 ,
当然也不排除 你中有我,我中有她,她中有你 这种复杂,甚至是更复杂的关系。
总之循环依赖就是会形成一个闭合的 环形 关系。
2. 循环依赖带来什么问题?
循环依赖会导致创建对象,进入死循环状态,无法创建成功。
在实例化对象A的时候,需要注入对象B,这时我们需要检查缓存中是否有对象B,如果缓存中没有对象B,这时我们需要实例化对象B,开始实例化对象B,对象B中又需要依赖A,但是这时缓存又没有构建好的A,又需要实例化A,实例化A又依赖B,B又依赖A,形成死循环,永远都不能成功创建A,B。
先实例化对象B于先实例化对象A是一样的。
3 . 如何解决循环依赖问题?
基于Java引用传递的特性,我们是可以通过缓存来解决循环依赖,我们可以将构建一半的对象放入缓存中,先进行下边的步骤,当下边的步骤创建完成后,缓存中的对象自然是完整的对象,因为缓存中我们只是存的引用地址,当对象创建好之后,半成品的对象就会变成成品对象。
3.1 一级缓存解决循环依赖
我们只通过一级缓存也是可以解决循环依赖问题的,但是会有很多问题,不够严谨,比如缓存中的对象有半成品,如果我们的对象没有打对应的标签,就可能会获取到半成品对象,引发一些诡异的问题。
- 无法很好的解决增强问题,即使用代理(必要时才提前创建代理对象)
- 无法清晰明确的区分出,哪个对象已经构建完成
3.2 二级缓存解决循环依赖问题
通过二级缓存其实我们已经可以很好的解决循环依赖问题,但是我们还是无法很好的解决增强问题,即使用代理(必要时才提前创建代理对象),通过缓存解决循环依赖问题,要求我们注入的对象必须是代理对象,才能实现增强的功能。
那么我们能不能在发生循环依赖的时候才去提前初始化代理对象呢?
那么我们什么时候才能知道发生了循环依赖呢?
当依赖注入时,我们从二级缓存中获取到对象,这时候就能判定发生了循环依赖,所以说我们在这时候构建代理对象。
如果没有发生循环依赖,我们是不需要从二级缓存中获取值的。
如果说我们在二级缓存中放入ObjectFactory,当从二级缓存中获取值的时候再通过工厂创建对象,这样不就可以解决循环依赖了吗?
看似这样可以解决循环依赖问题,并且可以在发生循环依赖的时候创建代理对象,但是新的问题又出现了。
新问题:如果是A依赖于B和C,而B依赖于A,同时C也依赖于A。
那么如果先创建的是A,那么创建A的时候会在二级缓存中生成A,那么创建B的时候也会去二级缓存获取A,创建C的时候也会去二级缓存中获取A获取到的
3.3 三级缓存解决循环依赖问题
Spring默认提供三级缓存解决循环依赖
4. Spring三级缓存源码分析
以下便是Spring中的源码
定义三级缓存
/** 三级缓存 */
/** 单例池 */
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 单例对象工厂 */
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** 早期(半成品)对象 */
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
获取单例对象,单例模式(懒加载双重检测锁)
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
@Nullable
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;
}
创建Bean的时候进行增强
public interface ObjectFactory<T> {
/**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return the resulting instance
* @throws BeansException in case of creation errors
*/
T getObject() throws BeansException;
}
getObject 的时候会触发AbstractAutowireCapableBeanFactory类createBean → doCreateBean方法
创建Bean的时候会判断是否支持三级缓存,支持会提前暴露对象
// 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) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
getEarlyBeanReference方法获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。
/**
* Obtain a reference for early access to the specified bean,
* typically for the purpose of resolving a circular reference.
* @param beanName the name of the bean (for error handling purposes)
* @param mbd the merged bean definition for the bean
* @param bean the raw bean instance
* @return the object to expose as bean reference
*/
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
此外创建对象时,还会进行检查(第二级缓存 和 原始对象 是否相等)
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
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.");
}
}
}
}
5. 为什么使用@Async的循环依赖不能行
@Async本质上也是通过代理模式进行增强
就是在检查的时候注入不成功,说明在两个代理对象不一致
这就是过早注入的弊端,在使用异步注解的时候,spring的默认实现是生成该类的代理对象,通过代理对象调用异步方法。也就是说这个代理对象不是单例的,一个先前已经被注入的代理,一个是刚生成的代理,而Spring三级缓存对Bean的属性注入要求都是单例的对象,这就造成了错误。
所以说,我们解决这个问题,只能去破坏循环依赖,或者优化类的加载顺序,如果先创建的是不加@Async的对象,则不会报错
6. 解决方案
- 重新设计,避免循环依赖,从根源上解决循环依赖问题
- 使用@Lazy注解,延迟加载
- 使用@DependsOn注解,指定加载的先后关系
- 修改文件名称,改变循环依赖类的加载顺序(Spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载)
需要自行解决的循环依赖:
- 多例循环依赖(不是单例模式)
- 构造器循环依赖
- @DependsOn产生的循环依赖
- @Async的循环依赖
- …