Spring解决循环依赖问题的四种方法汇总

 更新时间:2024年07月21日 13:58:35   作者:jc0803kevin  
这篇文章主要介绍了Spring解决循环依赖问题的四种方法汇总,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

Spring解决循环依赖问题的四种方法

  • @Lazy方式
  • 使用Setter/Field Injection
  • 使用@PostConstruct
  • 实现 ApplicationContextAware 和 InitializingBean

不管使用那种方式,最佳的还是通过调整代码结构,从根上设计从而达到避免。

定义两个相互依赖的 bean(通过构造函数注入)

package icu.kevin.springpart.circular.constructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CircularDependencyA {
    private CircularDependencyB circB;
    @Autowired
    public CircularDependencyA(CircularDependencyB circB) {
        this.circB = circB;
    }
}
package icu.kevin.springpart.circular.constructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CircularDependencyB {
    private CircularDependencyA circA;
    @Autowired
    public CircularDependencyB(CircularDependencyA circA) {
        this.circA = circA;
    }
}
package icu.kevin.springpart.circular.constructor;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = { "icu.kevin.springpart.circular.constructor" })
public class TestConfig {
}

编写测试用例,测试是否存在循环依赖

package icu.kevin.springpart.circular.constructor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyIntegrationTest {
    // Error creating bean with name 'circularDependencyA': Requested bean is currently in creation: Is there an unresolvable circular reference?
    @Test
    public void givenCircularDependency_whenConstructorInjection_thenItFails() {
        // 测试可以为空,因为在上下文加载期间将检测到循环依赖关系
    }
}

@Lazy方式

@Lazy告诉 Spring 延迟初始化其中一个 bean。因此,它不会完全初始化 Bean,而是创建一个代理将其注入另一个 Bean。注入的 Bean 只有在第一次需要时才会完全创建。

@Lazy 注解是通过建立一个中间代理层,来破解循环依赖的。

package icu.kevin.springpart.circular.lazy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
public class CircularDependencyA {
    private CircularDependencyB circB;
    @Autowired
    public CircularDependencyA(@Lazy CircularDependencyB circB) {
        this.circB = circB;
    }
}
package icu.kevin.springpart.circular.lazy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CircularDependencyB {
    private CircularDependencyA circA;
    @Autowired
    public CircularDependencyB(CircularDependencyA circA) {
        this.circA = circA;
    }
}
package icu.kevin.springpart.circular.lazy;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = { "icu.kevin.springpart.circular.lazy" })
public class TestConfig {
}
package icu.kevin.springpart.circular.lazy;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyIntegrationTest {
    // Error creating bean with name 'circularDependencyA': Requested bean is currently in creation: Is there an unresolvable circular reference?
    @Test
    public void givenCircularDependency_whenConstructorInjection_thenItFails() {
        // 测试可以为空,因为在上下文加载期间将检测到循环依赖关系
    }
}

启动可以看出,在上下文检查循环依赖是可以正常通过的

查看源码ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary首先会调用 isLazy 去判断一下是否需要延迟加载,
如果需要,则调用 buildLazyResolutionProxy 方法构建一个延迟加载的对象;

使用Setter/Field Injection

在setter注入的时候发生的依赖循环是可以被解决的,但只能解决单例情况的循环依赖。
对于setter注入造成的依赖是通过Spring容器提前暴露刚刚完成构造器但还没有进行注入的bean来完成的。通过提前暴露一个单例工厂让其他的bean可以引用到该Bean。

package icu.kevin.springpart.circular.setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CircularDependencyA {
    private CircularDependencyB circB;
    public CircularDependencyB getCircB() {
        return circB;
    }
    @Autowired
    public void setCircB(CircularDependencyB circB) {
        this.circB = circB;
    }
}
package icu.kevin.springpart.circular.setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CircularDependencyB {
    private CircularDependencyA circA;
    private String message = "Hi!";
    public CircularDependencyA getCircA() {
        return circA;
    }
    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}
package icu.kevin.springpart.circular.setter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = { "icu.kevin.springpart.circular.setter" })
public class TestConfig {
}
package icu.kevin.springpart.circular.setter;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyIntegrationTest {
    @Autowired
    private ApplicationContext context;
    @Test
    public void givenCircularDependency_whenConstructorInjection_thenItFails() {
        // Empty test; we just want the context to load
        CircularDependencyA circA = context.getBean(CircularDependencyA.class);
        Assert.assertEquals("Hi!", circA.getCircB().getMessage());
    }
}

使用@PostConstruct

package icu.kevin.springpart.circular.postConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class CircularDependencyA {
    @Autowired
    private CircularDependencyB circB;
    @PostConstruct
    public void init(){
        circB.setCircA(this);
    }
    public CircularDependencyB getCircB() {
        return circB;
    }
}
package icu.kevin.springpart.circular.postConstruct;
import org.springframework.stereotype.Component;
@Component
public class CircularDependencyB {
    private CircularDependencyA circA;
    private String message = "Hi!";
    public CircularDependencyA getCircA() {
        return circA;
    }
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}
package icu.kevin.springpart.circular.postConstruct;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = { "icu.kevin.springpart.circular.postConstruct" })
public class TestConfig {
}
package icu.kevin.springpart.circular.postConstruct;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyIntegrationTest {
    @Autowired
    private ApplicationContext context;
    @Test
    public void givenCircularDependency_whenConstructorInjection_thenItFails() {
        //
        CircularDependencyA circA = context.getBean(CircularDependencyA.class);
        Assert.assertEquals("Hi!", circA.getCircB().getMessage());
    }
}

实现 ApplicationContextAware 和 InitializingBean

如果其中一个 Bean 实现了 ApplicationContextAware,则该 Bean 可以访问 Spring 上下文,并可以从那里提取另一个 Bean。

通过实现 InitializingBean,我们指示该 Bean 在设置完所有属性后必须执行一些操作。在这种情况下,我们要手动设置依赖项。

package icu.kevin.springpart.circular.initializingBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {
    private CircularDependencyB circB;
    private ApplicationContext context;
    public CircularDependencyB getCircB() {
        return circB;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        circB = context.getBean(CircularDependencyB.class);
    }
    @Override
    public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
        context = ctx;
    }
}
package icu.kevin.springpart.circular.initializingBean;
import org.springframework.stereotype.Component;
@Component
public class CircularDependencyB {
    private CircularDependencyA circA;
    private String message = "Hi!";
    public CircularDependencyA getCircA() {
        return circA;
    }
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}
package icu.kevin.springpart.circular.initializingBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = { "icu.kevin.springpart.circular.initializingBean" })
public class TestConfig {
}
package icu.kevin.springpart.circular.initializingBean;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyIntegrationTest {
    @Autowired
    private ApplicationContext context;
    @Test
    public void givenCircularDependency_whenConstructorInjection_thenItFails() {
        // Empty test; we just want the context to load
        CircularDependencyA circA = context.getBean(CircularDependencyA.class);
        Assert.assertEquals("Hi!", circA.getCircB().getMessage());
    }
}

到此这篇关于Spring解决循环依赖问题的四种方法的文章就介绍到这了,更多相关Spring循环依赖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用Spring事件机制实现异步的方法

    使用Spring事件机制实现异步的方法

    这篇文章主要介绍了使用Spring事件机制实现异步的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-06-06
  • Maven构建SpringBoot集成MyBatis过程

    Maven构建SpringBoot集成MyBatis过程

    文章介绍了如何使用Maven创建一个Spring Boot项目,并集成MyBatis,首先,配置Maven环境,然后在pom.xml中添加MyBatis依赖,配置数据库连接信息,最后生成Mapper XML文件和DAO层代码
    2024-11-11
  • Mybatis如何配置连接池

    Mybatis如何配置连接池

    本文通过实例代码给大家详细介绍了mybatis配置连接池的方法,非常不错,具有参考借鉴价值,感兴趣的朋友参考下吧
    2016-12-12
  • Java中的static关键字用法总结

    Java中的static关键字用法总结

    这篇文章主要介绍了Java中的static关键字用法总结,static是Java50个关键字之一,static关键字可以用来修饰代码块表示静态代码块,修饰成员变量表示全局静态成员变量,修饰方法表示静态方法,需要的朋友可以参考下
    2023-11-11
  • Spring Web项目spring配置文件随服务器启动时自动加载

    Spring Web项目spring配置文件随服务器启动时自动加载

    这篇文章主要介绍了Spring Web项目spring配置文件随服务器启动时自动加载,加载spring的配置文件,并且只加载一次,从而提高程序效率。具体内容详情大家通过本文一起学习吧
    2018-01-01
  • Java8 Comparator源码演示及解析

    Java8 Comparator源码演示及解析

    这篇文章主要介绍了Java8 Comparator源码演示及解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • 12个SpringBoot配置文件的实用技巧分享

    12个SpringBoot配置文件的实用技巧分享

    配置文件是SpringBoot应用的核心组成部分,它决定了应用的行为、连接参数以及功能特性,本文为大家整理了12个SpringBoot配置文件的实用技巧,有需要的可以了解下
    2025-05-05
  • 基于Listener监听器生命周期(详解)

    基于Listener监听器生命周期(详解)

    下面小编就为大家带来一篇基于Listener监听器生命周期(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • MessageUtils.message("user.jcaptcha.expire")问题及解决

    MessageUtils.message("user.jcaptcha.expire")问题及解决

    文章介绍了若依项目中的国际化工具MessageUtils,用于根据不同语言环境获取验证码过期提示信息,通过Spring的MessageSource读取多语言配置文件实现,并详细描述了其作用、配置文件位置、底层原理及触发场景
    2026-04-04
  • Java equals()方法使用详解及总结

    Java equals()方法使用详解及总结

    这篇文章主要介绍了Java equals()方法使用详解及总结的相关资料,需要的朋友可以参考下
    2017-03-03

最新评论