SpringBoot构造器注入循环依赖及解决方案

 更新时间:2024年03月27日 11:35:22   作者:不懂原理的程序员.  
这篇文章主要介绍了SpringBoot构造器注入循环依赖及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

SpringBoot构造器注入循环依赖及解决

1. 循环依赖是什么?

Bean A 依赖 B,Bean B 依赖 A这种情况下出现循环依赖。

Bean A → Bean B → Bean A

更复杂的间接依赖造成的循环依赖如下。

Bean A → Bean B → Bean C → Bean D → Bean E → Bean A

(解释一下,三个以上其实和两个是一样的,多加几个@lazy就可以了,等到后面bean创建成功再创建)

2. 循环依赖会产生什么结果?

当Spring正在加载所有Bean时,Spring尝试以能正常创建Bean的顺序去创建Bean。

例如,有如下依赖:

Bean A → Bean B → Bean C

Spring先创建beanC,接着创建bean B(将C注入B中),最后创建bean A(将B注入A中)。

但当存在循环依赖时,Spring将无法决定先创建哪个bean。这种情况下,Spring将产生异常BeanCurrentlyInCreationException。

当使用构造器注入时经常会发生循环依赖问题。如果使用其它类型的注入方式能够避免这种问题。

简单构造器注入循环依赖实例

项目结构

如下:

1.首先定义两个相互通过构造器注入依赖的bean。

/**
 * @Author: lixs
 * @Date: 2021/4/6
 * @Description: 循环依赖类A
 */
@Component
public class CircularDependencyA {


    private CircularDependencyB circB;

    @Autowired
    public CircularDependencyA(@Lazy CircularDependencyB circB) {
        this.circB = circB;
    }
}
/**
 1. @Author: lixs
 2. @Date: 2021/4/6
 3. @Description: 循环依赖类B
 */
@Component
public class CircularDependencyB {

    private CircularDependencyA circA;

    @Autowired
    public CircularDependencyB(@Lazy CircularDependencyA circA) {
        this.circA= circA;
    }
}

2.创建配置类

/**
 1. @Author: lixs
 2. @Date: 2021/4/6
 3. @Description: 配置类
 */
@Configuration
@ComponentScan(basePackages = { "com.li.springboot.bean" })
public class TestConfig {
}

3.创建测试类

@SpringBootTest
@ContextConfiguration(classes = {TestConfig.class})
class ApplicationTests {

    @Test
    public void givenCircularDependency_whenConstructorInjection_thenItFails() {
        // Empty test; we just want the context to load
    }
}

正常运行结果:

上面就是报的是循环 依赖无法创建bean错误!

解决方案

1.重新设计

重新设计结构,不用这种方式去创建bean。

2.使用注解 @Lazy

一种最简单的消除循环依赖的方式是通过延迟加载。

在注入依赖时,先注入代理对象,当首次使用时再创建对象完成注入。

/**
 * @Author: lixs
 * @Date: 2021/4/6
 * @Description: 循环依赖类A
 */
@Component
public class CircularDependencyA {

    private CircularDependencyB circB;

    @Autowired
    public CircularDependencyA(@Lazy CircularDependencyB circB) {
        this.circB = circB;
    }
}

使用@Lazy后,运行代码,可以看到异常消除。

3.使用Setter/Field注入

Spring文档建议的一种方式是使用setter注入。当依赖最终被使用时才进行注入。对前文的样例代码少做修改,来观察测试效果。

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public void setCircB(CircularDependencyB circB) {
        this.circB = circB;
    }
 
    public CircularDependencyB getCircB() {
        return circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    private String message = "Hi!";
 
    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
 
    public String getMessage() {
        return message;
    }
}

测试类

@SpringBootTest
@ContextConfiguration(classes = {TestConfig.class})
class ApplicationTests {

     @Autowired
    ApplicationContext context;    //spring容器对象
 
    @Bean
    public CircularDependencyA getCircularDependencyA() {
        return new CircularDependencyA();
    }
 
    @Bean
    public CircularDependencyB getCircularDependencyB() {
        return new CircularDependencyB();
    }
 
    @Test
    public void testCircularDependency() {
    	//拿到bean对象
        CircularDependencyA circA = context.getBean(CircularDependencyA.class);
        //获取属性值
        String result=circA.getCircB().getMessage();
        System.out.println(result);
    }

4.使用@PostConstruct

@PostContruct是spring框架的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。

@Component
public class CircularDependencyA {
 
    @Autowired
    private CircularDependencyB circB;
 
    @PostConstruct
    public void init() {
        circB.setCircA(this);
    }
 
    public CircularDependencyB getCircB() {
        return circB;
    }
}
public class CircularDependencyB {
 
    private CircularDependencyA circA;
     
    private String message = "Hi!";
 
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
     
    public String getMessage() {
        return message;
    }
}

5.实现ApplicationContextAware与InitializingBean

(1)如果某个类实现了ApplicationContextAware接口,会在类初始化完成后调用setApplicationContext()方法进行操作

(2)如果某个类实现了InitializingBean接口,会在类初始化完成后,并在setApplicationContext()方法执行完毕后,调用afterPropertiesSet()方法进行操作

@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;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    private String message = "Hi!";
 
    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
 
    public String getMessage() {
        return message;
    }
}

总结

处理循环依赖有多种方式。

首先考虑是否能够通过重新设计依赖来避免循环依赖。

如果确实需要循环依赖,那么可以通过前文提到的方式来处理。

优先建议使用setter注入来解决。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持程序员之家。

相关文章

  • SpringBoot整合Activiti工作流框架的使用

    SpringBoot整合Activiti工作流框架的使用

    本文主要介绍了SpringBoot整合Activiti工作流框架的使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • Mybatis游标查询大量数据方式

    Mybatis游标查询大量数据方式

    这篇文章主要介绍了Mybatis游标查询大量数据方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • 运用示例详细总结Java多线程

    运用示例详细总结Java多线程

    本文主要讲解了Java多线程,该篇幅大量使用代码以及图片文字进行解析,可以让小伙伴们了解该方面的知识更加迅速快捷
    2021-08-08
  • Spring?Boot?实现字段唯一校验功能(实例代码)

    Spring?Boot?实现字段唯一校验功能(实例代码)

    这篇文章主要介绍了Spring?Boot?实现字段唯一校验,实现代码很简单,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • java图片对比度调整示例代码

    java图片对比度调整示例代码

    这篇文章主要给大家介绍了关于java图片对比度调整的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-07-07
  • TKmybatis的框架介绍和原理解析

    TKmybatis的框架介绍和原理解析

    tkmybatis是在mybatis框架的基础上提供了很多工具,让开发更加高效,下面来看看这个框架的基本使用,后面会对相关源码进行分析,感兴趣的同学可以看一下,挺不错的一个工具
    2020-12-12
  • springboot应用中使用过滤器的过程详解

    springboot应用中使用过滤器的过程详解

    过滤器通常用于实现跨切面的功能,例如身份验证、日志记录、请求和响应的修改、性能监控等,这篇文章主要介绍了springboot应用中使用过滤器,需要的朋友可以参考下
    2023-06-06
  • Java容器HashMap与HashTable详解

    Java容器HashMap与HashTable详解

    本文主要介绍HashMap 和 Hashtable的工作原理和使用方法,有兴趣的朋友可以参考
    2017-04-04
  • 解决java 命令行乱码的问题

    解决java 命令行乱码的问题

    这篇文章主要介绍了解决java 命令行乱码的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • spring @Lazy延迟注入的逻辑实现

    spring @Lazy延迟注入的逻辑实现

    有时候我们会在属性注入的时候添加@Lazy注解实现延迟注入,今天咱们通过阅读源码来分析下原因,感兴趣的可以了解一下
    2021-08-08

最新评论

?


http://www.vxiaotou.com