不是说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是一样的。

image.png

3 . 如何解决循环依赖问题?

基于Java引用传递的特性,我们是可以通过缓存来解决循环依赖,我们可以将构建一半的对象放入缓存中,先进行下边的步骤,当下边的步骤创建完成后,缓存中的对象自然是完整的对象,因为缓存中我们只是存的引用地址,当对象创建好之后,半成品的对象就会变成成品对象。

3.1 一级缓存解决循环依赖

我们只通过一级缓存也是可以解决循环依赖问题的,但是会有很多问题,不够严谨,比如缓存中的对象有半成品,如果我们的对象没有打对应的标签,就可能会获取到半成品对象,引发一些诡异的问题。

  • 无法很好的解决增强问题,即使用代理(必要时才提前创建代理对象)
  • 无法清晰明确的区分出,哪个对象已经构建完成

image.png

3.2 二级缓存解决循环依赖问题

通过二级缓存其实我们已经可以很好的解决循环依赖问题,但是我们还是无法很好的解决增强问题,即使用代理(必要时才提前创建代理对象),通过缓存解决循环依赖问题,要求我们注入的对象必须是代理对象,才能实现增强的功能。

那么我们能不能在发生循环依赖的时候才去提前初始化代理对象呢?

那么我们什么时候才能知道发生了循环依赖呢?

当依赖注入时,我们从二级缓存中获取到对象,这时候就能判定发生了循环依赖,所以说我们在这时候构建代理对象。

如果没有发生循环依赖,我们是不需要从二级缓存中获取值的。

如果说我们在二级缓存中放入ObjectFactory,当从二级缓存中获取值的时候再通过工厂创建对象,这样不就可以解决循环依赖了吗?

看似这样可以解决循环依赖问题,并且可以在发生循环依赖的时候创建代理对象,但是新的问题又出现了。

新问题:如果是A依赖于B和C,而B依赖于A,同时C也依赖于A。

那么如果先创建的是A,那么创建A的时候会在二级缓存中生成A,那么创建B的时候也会去二级缓存获取A,创建C的时候也会去二级缓存中获取A获取到的

image.png

3.3 三级缓存解决循环依赖问题

Spring默认提供三级缓存解决循环依赖

image.png

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本质上也是通过代理模式进行增强

就是在检查的时候注入不成功,说明在两个代理对象不一致

image.png

这就是过早注入的弊端,在使用异步注解的时候,spring的默认实现是生成该类的代理对象,通过代理对象调用异步方法。也就是说这个代理对象不是单例的,一个先前已经被注入的代理,一个是刚生成的代理,而Spring三级缓存对Bean的属性注入要求都是单例的对象,这就造成了错误。

所以说,我们解决这个问题,只能去破坏循环依赖,或者优化类的加载顺序,如果先创建的是不加@Async的对象,则不会报错

以下代码是@Async的拦截器的实现源码,当获取带有@Async注解的对象时,都会构建异步任务,并生成一个新的代理对象

image.png

6. 解决方案

  • 重新设计,避免循环依赖,从根源上解决循环依赖问题
  • 使用@Lazy注解,延迟加载
  • 使用@DependsOn注解,指定加载的先后关系
  • 修改文件名称,改变循环依赖类的加载顺序(Spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载)

需要自行解决的循环依赖:

  • 多例循环依赖(不是单例模式)
  • 构造器循环依赖
  • @DependsOn产生的循环依赖
  • @Async的循环依赖
  • ...
目录
相关文章
|
监控 安全 Java
解决 Spring Boot 中 SecurityConfig 循环依赖问题的详解
本文详细解析了在 Spring Boot 中配置 `SecurityConfig` 时可能遇到的循环依赖问题。通过分析错误日志与代码,指出问题根源在于 `SecurityConfig` 类中不当的依赖注入方式。文章提供了多种解决方案:移除 `configureGlobal` 方法、定义 `DaoAuthenticationProvider` Bean、使用构造函数注入以及分离配置类等。此外,还讨论了 `@Lazy` 注解和允许循环引用的临时手段,并强调重构以避免循环依赖的重要性。通过合理设计 Bean 依赖关系,可确保应用稳定启动并提升代码可维护性。
995 0
|
9月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
1601 5
|
Java Maven 微服务
微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的 maven 依赖
在项目中使用Swagger2工具时,需导入Maven依赖。尽管官方最高版本为2.8.0,但其展示效果不够理想且稳定性欠佳。实际开发中常用2.2.2版本,因其稳定且界面友好。以下是围绕2.2.2版本的Maven依赖配置,包括`springfox-swagger2`和`springfox-swagger-ui`两个模块。
629 0
|
9月前
|
存储 缓存 Java
Spring中@Cacheable、@CacheEvict以及其他缓存相关注解的实用介绍
缓存是提升应用性能的重要技术,Spring框架提供了丰富的缓存注解,如`@Cacheable`、`@CacheEvict`等,帮助开发者简化缓存管理。本文介绍了如何在Spring中配置缓存管理器,使用缓存注解优化数据访问,并探讨了缓存的最佳实践,以提升系统响应速度与可扩展性。
425 0
Spring中@Cacheable、@CacheEvict以及其他缓存相关注解的实用介绍
|
消息中间件 缓存 NoSQL
基于Spring Data Redis与RabbitMQ实现字符串缓存和计数功能(数据同步)
总的来说,借助Spring Data Redis和RabbitMQ,我们可以轻松实现字符串缓存和计数的功能。而关键的部分不过是一些"厨房的套路",一旦你掌握了这些套路,那么你就像厨师一样可以准备出一道道饕餮美食了。通过这种方式促进数据处理效率无疑将大大提高我们的生产力。
380 32
|
11月前
|
存储 缓存 NoSQL
Spring Cache缓存框架
Spring Cache是Spring体系下的标准化缓存框架,支持多种缓存(如Redis、EhCache、Caffeine),可独立或组合使用。其优势包括平滑迁移、注解与编程两种使用方式,以及高度解耦和灵活管理。通过动态代理实现缓存操作,适用于不同业务场景。
745 0
|
缓存 架构师 Java
图解 Spring 循环依赖,一文吃透!
Spring 循环依赖如何解决,是大厂面试高频,本文详细解析,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 Spring 循环依赖,一文吃透!
|
缓存 Java 应用服务中间件
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——依赖导入和Thymeleaf相关配置
在Spring Boot中使用Thymeleaf模板,需引入依赖`spring-boot-starter-thymeleaf`,并在HTML页面标签中声明`xmlns:th=&quot;http://www.thymeleaf.org&quot;`。此外,Thymeleaf默认开启页面缓存,开发时建议关闭缓存以实时查看更新效果,配置方式为`spring.thymeleaf.cache: false`。这可避免因缓存导致页面未及时刷新的问题。
519 0
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
395 2
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
582 4