首页 > 其他分享 >Spring解决循环依赖问题的四种方法

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

时间:2024-07-16 11:28:50浏览次数:12  
标签:依赖 Spring springframework class context import org public 四种

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,springframework,class,context,import,org,public,四种
From: https://blog.csdn.net/jc0803kevin/article/details/140461537

相关文章

  • Spring Task定时任务框架
    文章目录简单介绍说明说明:作用:一般应用场景:重点:cron表达式简介与举例:通配符:cron表达式案例:自动生成工具使用举例:准备小案例:简单介绍说明说明:SpringTask是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。作用:定时自动执行某段代码一......
  • Springboot 校园安全通事件报告小程序-计算机毕业设计源码02445
    Springboot校园安全通事件报告小程序系统摘 要随着中国经济的飞速增长,消费者的智能化水平不断提高,许多智能手机和相关的软件正在得到更多的关注和支持。其中,校园安全通事件报告小程序系统更是深得消费者的喜爱,它的出现极大地改善了消费者的生活质量,同时,它还创造了一种快......
  • 聊聊springboot项目脱离配置中心,如何实现属性动态刷新
    前言如果大家有开发过微服务项目,那对配置中心应该是耳熟能详了,配置中心有个很有用的能力,就是热更新属性,即不重启服务,就能做到属性的动态变更。而我们今天讲的话题是,怎么样不使用配置中心,也能达到如上的效果如何实现属性的热更新如果我们属性是配置在配置文件中,我们可以通过监听......
  • Spring简介
    Spring框架由RodJohnson开发,2004年发布了Spring框架的第一版。Spring是一个从实际开发中抽取出来的框架,因此它完成了大量开发中的通用步骤,留给开发者的仅仅是与特定应用相关的部分,从而大大提高了企业应用的开发效率。Spring总结起来优点如下:低侵入式设计,代码的污染极低。......
  • SpringBoot+Vue母婴用品商城(前后端分离)
    技术栈JavaSpringBootMavenMySQLVueElement-UIShiroMybatis-Plus系统角色功能用户管理员系统功能截图......
  • 基于SpringBoot+Vue+uniapp的邮件过滤系统的详细设计和实现(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • 基于SpringBoot+Vue+uniapp的美食推荐小程序的详细设计和实现(源码+lw+部署文档+讲解
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • 基于SpringBoot+Vue+uniapp的生鲜食品订购的详细设计和实现(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • SpringBoot 配置⽂件
    主要介绍:1.SpringBoot配置⽂件的格式以及对应的语法2.了解两个配置⽂件格式的差异1.配置⽂件作⽤        计算机上有数以千计的配置⽂件,我们使⽤的绝⼤多数软件,⽐如浏览器,微信,Idea,甚⾄电脑,⼿机,都离不开配置⽂件.        ......
  • springboot常用注解大全(超详细, 30个)
    SpringBoot注解主要用于简化配置、自动装配组件和实现声明式服务。以下是详细的介绍:1、Springboot注解核心注解1.@SpringBootApplication作用:标注一个主程序类,表明这是一个SpringBoot应用程序的入口。功能:这是一个复合注解,组合了@Configuration、@EnableAutoConfigur......