spring依赖注入——循环依赖

简介: 上一篇博客简单地分析了下依赖注入。但是对于依赖注入的很多细节,都没有深入的分析。这一篇博客会继续分析spring的依赖注入。这篇博客会解决分析getBean缓存时候遗留下来的循环依赖问题。

上一篇博客简单地分析了下依赖注入。但是对于依赖注入的很多细节,都没有深入的分析。这一篇博客会继续分析spring的依赖注入。这篇博客会解决分析getBean缓存时候遗留下来的循环依赖问题。

循环依赖分析

首先明确下,只有单例情况下,spring才会试着去解决循环依赖问题,多例是不会去解决循环依赖的。这个也好理解,如果是多例的话,比如a -> b 并且 b -> a 那么,当A a=new A(); 之后要注入b,b却是多例的,那么究竟该注入哪个B是不确定的。如下图:


img_9438c0bc80a002bbfeef214bee308c07.png

接下来我们分析,为啥会有循环依赖的问题。

先来分析没有循环依赖的问题

public static class A{
    
    private B b;

    //省略get和set方法
}

public static class B{
}

这个时候,如果spring先初始化A,然后会发现A依赖于B,然后就会初始化B,最后注入到A里。

整个流程用代码表示大概如下所示:

A a = 创建A
B b = 创建B
        -----> 创建A子流程  a.setB(b);

但是假设B也依赖了A呢?即

public static class B{
    
    private A a;

}

那样依赖注入过程就会变成(简单示例)

A a = 创建A
B b = 创建B
       ---->  创建B子流程 b.setA(????);  //  这个时候A还没创建完成呢
a.setB(b);

那么如何解决呢?很简单,把那个还没创建完的A(只是new了,但是没有进行依赖注入的A)set到B里就好了。

img_b43bbd92196c11922c79ad0894501ac6.png

弄清了这个流程之后,我们再来分析spring是如何进行依赖注入的。

spring对引用类型的注入

这里我先从spring对引用属性的注入开始。

<bean id="person" class="com.hdj.learn.spring.demo.Person">
    <property name="car" ref="car"></property>
</bean>

即ref的注入

private Object resolveReference(Object argName, RuntimeBeanReference ref) {
    try {
        String refName = ref.getBeanName();
        refName = String.valueOf(doEvaluate(refName));
        if (ref.isToParent()) {
            if (this.beanFactory.getParentBeanFactory() == null) {
                throw new BeanCreationException(
                        this.beanDefinition.getResourceDescription(), this.beanName,
                        "Can't resolve reference to bean '" + refName +
                        "' in parent factory: no parent factory available");
            }
            return this.beanFactory.getParentBeanFactory().getBean(refName);
        }
        else {
            Object bean = this.beanFactory.getBean(refName);
            this.beanFactory.registerDependentBean(refName, this.beanName);
            return bean;
        }
    }
    catch (BeansException ex) {
        throw new BeanCreationException(
                this.beanDefinition.getResourceDescription(), this.beanName,
                "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);
    }
}

实现很简单,就是从beanFactory里获取要依赖的对象

我们再来回顾下流程

img_53963483c02865d9e3044b62581aa54d.png

问题是,当走到6时候,似乎又会回到1,这样不就死循环了么?

重点就是,第六步的获取A,我们回到doGetBean方法中。

getSingleton 方法

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //已创建的对象里面找下
    Object singletonObject = this.singletonObjects.get(beanName);
    //没找到,并且当前类正在被创建
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

我们在分析初始化bean的缓存部分时,曾分析过这几个缓存。当时其实只知道了singletonObjects是存储了已经创建了的对象。

现在让我们回头再看看这些缓存。

首先看看singletonFactories赋值的地方。

doCreateBean

// 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.isDebugEnabled()) {
        logger.debug("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    addSingletonFactory(beanName, new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
            //默认实现返回bean
            return getEarlyBeanReference(beanName, mbd, bean);
        }
    });
}

在创建bean时候,有这样一段代码,当需要进行提前暴露时候(当前创建对象单例 + 允许循环引用 + 当前类正在被创建)会调用addSingletonFactory方法

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

这里就碰见了我们要分析的singletonFactories,它存储了beanName -> 此处的匿名内部类singletonFactory。

singletonFactory

我们再回到getSingleton方法,以及之前绘制的图

ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
    singletonObject = singletonFactory.getObject();
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);
}

逻辑很简单


img_99cf20c8d78547efc8aebdd0c546555f.png

三级缓存

我们经常听说spring通过三级缓存解决了循环依赖,其实三级缓存非常简单。就是指我们分析过的

Map<String, Object> singletonObjects
Map<String, ObjectFactory<?>> singletonFactories
Map<String, Object> earlySingletonObjects

这里分别举这样三个例子。
A 依赖 B(B不依赖A)


img_76690b1a16c8ca5b101a42aac6051e79.png
一级缓存

A 依赖 B && B依赖A


img_5a62e5238f3fe44e0d20031a350f18a2.png
三级缓存

A依赖B && B依赖A + B依赖C && C 依赖 A

img_5fe77e7b11b5a75716b388b13143d8bd.png
二级缓存

至此所谓的三级缓存及其使用,应该就非常的清楚了。

总结下

这篇博客分析了spring依赖注入中,循环依赖的问题,分析了在spring初始化bean时,各级缓存的作用。应该算是挺清晰的了。

目录
相关文章
|
6月前
|
监控 安全 Java
解决 Spring Boot 中 SecurityConfig 循环依赖问题的详解
本文详细解析了在 Spring Boot 中配置 `SecurityConfig` 时可能遇到的循环依赖问题。通过分析错误日志与代码,指出问题根源在于 `SecurityConfig` 类中不当的依赖注入方式。文章提供了多种解决方案:移除 `configureGlobal` 方法、定义 `DaoAuthenticationProvider` Bean、使用构造函数注入以及分离配置类等。此外,还讨论了 `@Lazy` 注解和允许循环引用的临时手段,并强调重构以避免循环依赖的重要性。通过合理设计 Bean 依赖关系,可确保应用稳定启动并提升代码可维护性。
498 0
|
7月前
|
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`两个模块。
234 0
|
7月前
|
缓存 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`。这可避免因缓存导致页面未及时刷新的问题。
298 0
|
10月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
11月前
|
缓存 架构师 Java
图解 Spring 循环依赖,一文吃透!
Spring 循环依赖如何解决,是大厂面试高频,本文详细解析,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 Spring 循环依赖,一文吃透!
|
10月前
|
Java 数据库 数据安全/隐私保护
轻松掌握Spring依赖注入:打造你的登录验证系统
本文以轻松活泼的风格,带领读者走进Spring框架中的依赖注入和登录验证的世界。通过详细的步骤和代码示例,我们从DAO层的创建到Service层的实现,再到Spring配置文件的编写,最后通过测试类验证功能,一步步构建了一个简单的登录验证系统。文章不仅提供了实用的技术指导,还以口语化和生动的语言,让学习变得不再枯燥。
177 2
|
10月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
220 2
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
1009 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
668 1
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
116 4