栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

不是说Spring三级缓存可以解决循环依赖吗?怎么我写的就GG了

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

不是说Spring三级缓存可以解决循环依赖吗?怎么我写的就GG了

大家都知道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中的源码

定义三级缓存

 
 

private final Map singletonObjects = new ConcurrentHashMap<>(256);
​
 

private final Map> singletonFactories = new HashMap<>(16);
​
 

private final Map earlySingletonObjects = new ConcurrentHashMap<>(16);
复制代码

获取单例对象,单例模式(懒加载双重检测锁)

    
    @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 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生成代理对象。

    
    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 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的循环依赖
  • ...

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/681015.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号