Spring源码剖析之Spring处理循环依赖的问题

yizhihongxing

下面就是关于“Spring源码剖析之Spring处理循环依赖的问题”的完整攻略。

标题:Spring源码剖析之Spring处理循环依赖的问题

什么是循环依赖?

循环依赖指的是在Spring容器初始化bean时,A对象依赖B对象,同时B对象又依赖A对象。这种情况下,Spring无法推断依赖关系,会抛出BeanCurrentlyInCreationException异常。

Spring提供了三种处理循环依赖的方法:

  1. 构造函数方式的循环依赖;
  2. Set方式的循环依赖;
  3. 通过AOP CGLIB代理方式的循环依赖。

构造函数方式的循环依赖

构造函数方式的循环依赖是指,两个或多个bean在实例化的时候就互相依赖。当Spring创建Bean时,发现A依赖B,于是会先去创建B对象;而B依赖A,于是也会去创建A对象。这就会导致两个对象互相等待初始化。

针对构造函数方式的循环依赖,Spring提供了一个“提前暴露Bean”(Eagerly-Exposed Bean,EJB)的机制。在将JavaBean实例化的时候,会分为实例化和初始化两步,在初始化过程中注入属性信息。这时候如果A依赖B,而B又依赖A,那么再A实例化完并且成功初始化时,B依赖A的属性已经赋值成功,这时候B就可以正常地初始化注入完成。因此,可以通过将需要提前初始化的 Bean、早些暴露出来,从而解决构造函数方式的循环依赖问题。

Set方式的循环依赖

针对Set方式的循环依赖,Spring采用了一个“两步注入”(two-stage injection)的机制。当Bean初始化完成后,容器会将Bean实例化后的对象放入到“实例化完成”的Map容器中,然后开始Bean初始化的工作。在初始化完成前,Spring还会特殊处理一下当前Bean所需要的其他Bean。当注入Bean B时发现 B 还需要注入 A,而 A 目前还未完成注入,此时Spring先自动将B注入一个代理对象,然后将B放入到“正在初始化的Bean”Map容器中,等到 B 初始化完成后,再让 B 代理对象把 A 赋值给 B,从而解决了循环依赖的问题。

AOP CGLIB代理方式的循环依赖

当依赖关系出现循环调用时,我们还可以采用 AOP+CGLIB 的方式控制依赖关系的实例化和属性注入。Spring利用AOP的技术,对需要循环回调的Bean通过CGLIB动态代理构建一个代理对象叫做“本地代理”,然后使用本地代理对象解析AOP方法时注入所依赖的Bean对象。

示例说明

下面是两个示例说明,分别说明了构造函数方式和Set方式的循环依赖。

示例1:构造函数方式的循环依赖

public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}

@Configuration
public class AppConfig {
    @Bean
    public A a() {
        return new A(b());
    }

    @Bean
    public B b() {
        return new B(a());
    }
}

当我们在执行AppConfig@Bean时,就会抛出循环依赖的异常。

如何解决呢?我们可以使用@Configuration的属性@DependsOn来解决。

@Configuration
public class AppConfig {
    @Bean
    public A a() {
        return new A(b());
    }

    @Bean
    @DependsOn("a")
    public B b() {
        return new B(a());
    }
}

在这个例子中,我们告诉Spring在初始化Bean b的时候,先初始依赖的Bean a,这样就不会再产生循环依赖的问题。

示例2:Set方式的循环依赖

public class A {
   private B b;
   public A() {
   }

   public void setB(B b) {
     this.b = b;
   }
}

public class B {
   private A a;

   public B() {
   }

   public void setA(A a) {
     this.a = a;
   }
}

如果我们将上面的A和B两个类放入Spring容器中,即:

<bean id="a" class="A" />
<bean id="b" class="B" />

则会抛出异常提示:

Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

解决方式:

我们可以使用@Autowired@Resource来注入依赖,如下。

public class A {
    @Resource
    private B b;
}

public class B {
    @Autowired
    private A a;
}

这样,Spring会在执行到A或B的时候,先实例化自己再注入依赖的Bean,从而避免了循环依赖的问题。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring源码剖析之Spring处理循环依赖的问题 - Python技术站

(0)
上一篇 2023年5月31日
下一篇 2023年5月31日

相关文章

  • 详解Java使用JMH进行基准性能测试

    详解Java使用JMH进行基准性能测试 性能测试对于Java应用程序的开发和优化是至关重要的。在Java中,可以使用JMH框架进行严密的基准测试,以确保在实际生产环境中获得最高的性能和最小的延迟。 介绍JMH框架 JMH是一个专业级的基准测试框架,它可以提供准确的基准测试结果。它提供了多种的方式来测试不同的Java代码片段,包括方法调用、对象实例化、算法等。…

    Java 2023年5月26日
    00
  • nginx负载均衡下的webshell上传的实现

    nginx是一个常用的反向代理服务器,在web应用中常常被用作负载均衡的前端。在nginx负载均衡下进行webshell的上传需要以下步骤: 1. 伪造HTTP请求 攻击者需要通过伪造HTTP请求方式进行上传webshell。伪造HTTP请求通常会使用Burp Suite等类似的工具,伪造请求包括请求方式、请求头、请求内容等,攻击者需要抓取正常用户发出的上传…

    Java 2023年6月16日
    00
  • java原装代码完成pdf在线预览和pdf打印及下载

    Java原装代码可以实现PDF在线预览、打印和下载功能。以下是实现PDF在线预览、打印和下载功能的详细攻略。 准备工作 在实现PDF在线预览、打印和下载功能之前,需要完成以下准备工作: 下载并安装Java SDK; 下载并安装Tomcat服务器; 下载并安装Apache POI库。 实现PDF在线预览 要实现PDF在线预览功能,需要使用PDF.js这个开源库…

    Java 2023年6月15日
    00
  • Window下安装JDK1.8+Tomcat9.0.27+Mysql5.7.28的教程图解

    下面我将详细讲解“Window下安装JDK1.8+Tomcat9.0.27+Mysql5.7.28的教程图解”的完整攻略。 前置要求 在安装这三个软件之前,需要先确定你的电脑已经满足以下几个前置要求: 操作系统:Windows 7/8/10 硬件配置:2GB 以上内存,至少 3GB 的硬盘空间 网络环境:需要能够联网,方便软件下载和安装 JDK1.8 的安装…

    Java 2023年6月2日
    00
  • Spring Boot 自动配置的实现

    Spring Boot自动配置是Spring Boot的一个重要特性,它可以帮助我们快速构建应用程序,减少配置工作。以下是Spring Boot自动配置的实现的详细攻略: 自动配置原理 Spring Boot自动配置的原理是基于Spring的条件化配置机制。Spring Boot会根据应用程序的classpath、配置文件和其他条件来自动配置应用程序。如果应…

    Java 2023年5月15日
    00
  • JSP实用教程之简易文件上传组件的实现方法(附源码)

    让我来详细讲解一下“JSP实用教程之简易文件上传组件的实现方法(附源码)”的完整攻略。 什么是文件上传组件? 文件上传组件通常用于在网站上让用户上传文件,如图片、文档等。在JSP中,我们可以通过一些Java类和第三方库来实现上传功能。而本文将讲解一个简易的文件上传组件的实现方法。 实现步骤 在JSP页面中添加表单、输入框和上传按钮。 <form met…

    Java 2023年6月15日
    00
  • 一文详解Java中字符串的基本操作

    一文详解Java中字符串的基本操作 字符串定义 在Java中,字符串是一种数据类型,用来表示一系列的字符组合。在Java中,字符串是用双引号(” “)括起来的,可以包含任意数量的字符。 String str1 = "hello world"; 字符串拼接 在Java中,字符串可以通过加号(+)进行拼接。 String str1 = &qu…

    Java 2023年5月26日
    00
  • 浅谈对Lambda表达式的理解

    浅谈对Lambda表达式的理解 什么是Lambda表达式 Lambda表达式是一种匿名函数,它可以像一个值一样被传递和使用。Lambda表达式的语法是(parameter1, parameter2, …) -> expression。 Lambda表达式的作用 Lambda表达式可以用来简化代码,使代码更加简洁、易读。它可以替代一些比较繁琐的代码,…

    Java 2023年5月26日
    00
合作推广
合作推广
分享本页
返回顶部