SpringBoot2.6.0起及新版本中解决循环依赖问题

SpringBoot2.6.0起及新版本中解决循环依赖问题

1. 循环依赖

Spring boot 2.6.0之前默认支持循环依赖处理的,但是Spring boot 2.6.0开始则默认不支持循环依赖的,需要做特殊处理

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

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。

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

2. 循环依赖案例

1. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.8</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.yuan</groupId>
	<artifactId>yuan-boot-loop-dependency</artifactId>
	<version>1.0.0</version>
	<name>yuan-boot-loop-dependency</name>
	<description>yuan-boot-loop-dependency</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

2. Service中两个类之间相互依赖

  1. 新建产品Service与订单Service
  2. 两个类之间通过属性注入彼此

1. ProductService

ProductService中通过属性注入方式注入OrderService

package com.yuan.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ProductService {
    @Autowired
    private OrderService orderService;
}

2. OrderService

OrderService通过属性注入方式注入ProductService

package com.yuan.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    @Autowired
    private ProductService productService;
}

3. 启动是两个类之间循环依赖了

  1. 启动控制台打印如下:
D:\devsoftware\Java\jdk1.8.0_333\bin\java.exe -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:61391,suspend=y,server=n -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:D:\devsoftware\JetBrains\IntelliJIDEA2022.1.2\plugins\java\lib\rt\debugger-agent.jar=file:/C:/Users/23013/AppData/Local/Temp/capture.props -Dfile.encoding=UTF-8 -classpath D:\devsoftware\Java\jdk1.8.0_333\jre\lib\charsets.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\deploy.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\ext\access-bridge-64.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\ext\cldrdata.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\ext\dnsns.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\ext\jaccess.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\ext\jfxrt.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\ext\localedata.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\ext\nashorn.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\ext\sunec.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\ext\sunjce_provider.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\ext\sunmscapi.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\ext\sunpkcs11.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\ext\zipfs.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\javaws.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\jce.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\jfr.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\jfxswt.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\jsse.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\management-agent.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\plugin.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\resources.jar;D:\devsoftware\Java\jdk1.8.0_333\jre\lib\rt.jar;E:\MyIdeaProjects\boot\yuan-boot-integrate\yuan-boot-web\yuan-boot-loop-dependency\target\classes;E:\MavenWarehouse\MavenLocalRepository\org\springframework\boot\spring-boot-starter-thymeleaf\2.7.8\spring-boot-starter-thymeleaf-2.7.8.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\boot\spring-boot-starter\2.7.8\spring-boot-starter-2.7.8.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\boot\spring-boot\2.7.8\spring-boot-2.7.8.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\boot\spring-boot-autoconfigure\2.7.8\spring-boot-autoconfigure-2.7.8.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\boot\spring-boot-starter-logging\2.7.8\spring-boot-starter-logging-2.7.8.jar;E:\MavenWarehouse\MavenLocalRepository\ch\qos\logback\logback-classic\1.2.11\logback-classic-1.2.11.jar;E:\MavenWarehouse\MavenLocalRepository\ch\qos\logback\logback-core\1.2.11\logback-core-1.2.11.jar;E:\MavenWarehouse\MavenLocalRepository\org\apache\logging\log4j\log4j-to-slf4j\2.17.2\log4j-to-slf4j-2.17.2.jar;E:\MavenWarehouse\MavenLocalRepository\org\apache\logging\log4j\log4j-api\2.17.2\log4j-api-2.17.2.jar;E:\MavenWarehouse\MavenLocalRepository\org\slf4j\jul-to-slf4j\1.7.36\jul-to-slf4j-1.7.36.jar;E:\MavenWarehouse\MavenLocalRepository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\MavenWarehouse\MavenLocalRepository\org\yaml\snakeyaml\1.30\snakeyaml-1.30.jar;E:\MavenWarehouse\MavenLocalRepository\org\thymeleaf\thymeleaf-spring5\3.0.15.RELEASE\thymeleaf-spring5-3.0.15.RELEASE.jar;E:\MavenWarehouse\MavenLocalRepository\org\thymeleaf\thymeleaf\3.0.15.RELEASE\thymeleaf-3.0.15.RELEASE.jar;E:\MavenWarehouse\MavenLocalRepository\org\attoparser\attoparser\2.0.5.RELEASE\attoparser-2.0.5.RELEASE.jar;E:\MavenWarehouse\MavenLocalRepository\org\unbescape\unbescape\1.1.6.RELEASE\unbescape-1.1.6.RELEASE.jar;E:\MavenWarehouse\MavenLocalRepository\org\slf4j\slf4j-api\1.7.36\slf4j-api-1.7.36.jar;E:\MavenWarehouse\MavenLocalRepository\org\thymeleaf\extras\thymeleaf-extras-java8time\3.0.4.RELEASE\thymeleaf-extras-java8time-3.0.4.RELEASE.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\boot\spring-boot-starter-web\2.7.8\spring-boot-starter-web-2.7.8.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\boot\spring-boot-starter-json\2.7.8\spring-boot-starter-json-2.7.8.jar;E:\MavenWarehouse\MavenLocalRepository\com\fasterxml\jackson\core\jackson-databind\2.13.4.2\jackson-databind-2.13.4.2.jar;E:\MavenWarehouse\MavenLocalRepository\com\fasterxml\jackson\core\jackson-annotations\2.13.4\jackson-annotations-2.13.4.jar;E:\MavenWarehouse\MavenLocalRepository\com\fasterxml\jackson\core\jackson-core\2.13.4\jackson-core-2.13.4.jar;E:\MavenWarehouse\MavenLocalRepository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.13.4\jackson-datatype-jdk8-2.13.4.jar;E:\MavenWarehouse\MavenLocalRepository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.13.4\jackson-datatype-jsr310-2.13.4.jar;E:\MavenWarehouse\MavenLocalRepository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.13.4\jackson-module-parameter-names-2.13.4.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\boot\spring-boot-starter-tomcat\2.7.8\spring-boot-starter-tomcat-2.7.8.jar;E:\MavenWarehouse\MavenLocalRepository\org\apache\tomcat\embed\tomcat-embed-core\9.0.71\tomcat-embed-core-9.0.71.jar;E:\MavenWarehouse\MavenLocalRepository\org\apache\tomcat\embed\tomcat-embed-el\9.0.71\tomcat-embed-el-9.0.71.jar;E:\MavenWarehouse\MavenLocalRepository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.71\tomcat-embed-websocket-9.0.71.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\spring-web\5.3.25\spring-web-5.3.25.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\spring-beans\5.3.25\spring-beans-5.3.25.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\spring-webmvc\5.3.25\spring-webmvc-5.3.25.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\spring-aop\5.3.25\spring-aop-5.3.25.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\spring-context\5.3.25\spring-context-5.3.25.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\spring-expression\5.3.25\spring-expression-5.3.25.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\spring-core\5.3.25\spring-core-5.3.25.jar;E:\MavenWarehouse\MavenLocalRepository\org\springframework\spring-jcl\5.3.25\spring-jcl-5.3.25.jar;D:\devsoftware\JetBrains\IntelliJIDEA2022.1.2\lib\idea_rt.jar com.yuan.YuanBootLoopDependencyApplication
Connected to the target VM, address: '127.0.0.1:61391', transport: 'socket'

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.8)

2023-02-12 09:28:20.189  INFO 1764 --- [           main] c.y.YuanBootLoopDependencyApplication    : Starting YuanBootLoopDependencyApplication using Java 1.8.0_333 on jinshengyuan with PID 1764 (E:\MyIdeaProjects\boot\yuan-boot-integrate\yuan-boot-web\yuan-boot-loop-dependency\target\classes started by jinshengyuan in E:\MyIdeaProjects\boot\yuan-boot-integrate)
2023-02-12 09:28:20.192  INFO 1764 --- [           main] c.y.YuanBootLoopDependencyApplication    : No active profile set, falling back to 1 default profile: "default"
2023-02-12 09:28:20.954  INFO 1764 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-02-12 09:28:20.961  INFO 1764 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-02-12 09:28:20.961  INFO 1764 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.71]
2023-02-12 09:28:21.114  INFO 1764 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-02-12 09:28:21.114  INFO 1764 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 879 ms
2023-02-12 09:28:21.155  WARN 1764 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'orderService': Unsatisfied dependency expressed through field 'productService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'productService': Unsatisfied dependency expressed through field 'orderService'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'orderService': Requested bean is currently in creation: Is there an unresolvable circular reference?
2023-02-12 09:28:21.157  INFO 1764 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2023-02-12 09:28:21.165  INFO 1764 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2023-02-12 09:28:21.181 ERROR 1764 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  orderService (field private com.yuan.service.ProductService com.yuan.service.OrderService.productService)
↑     ↓
|  productService (field private com.yuan.service.OrderService com.yuan.service.ProductService.orderService)
└─────┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

Disconnected from the target VM, address: '127.0.0.1:61391', transport: 'socket'

Process finished with exit code 1

3. 循环依赖处理方式

解决方式参考:https://www.baeldung.com/circular-dependencies-in-spring

最好的方法是重新设计、重构代码,进行解耦,消除循环依赖,如果没有时间重构,可以使用下面的方法

  1. 最简单的方法在yaml或properties中配置spring.main.allow-circular-references=true
  2. 懒加载(基于Xml的方式)_在你的配置文件中,在互相依赖的两个bean的任意一个加上lazy-init属性
  3. 懒加载(基于注解的方式)_在你注入bean时,在互相依赖的两个bean上加上@Lazy注解也可以
  4. 通过实现ApplicationContextAware接口,并结合单例模式,在私有构造方法中通过applicationContext.getBen()方式
  5. 同时实现ApplicationContextAwareInitializingBean 接口,具体查看https://www.baeldung.com/circular-dependencies-in-spring文档
  6. 使用@PostConstruct注解

1. application.yml配置方式解决

通过下面配置后可注入循环依赖,服务正常启动

spring:
  main:
    # 默认为false,不支持循环依赖,改为true后可注入循环依赖
    allow-circular-references: true

2. 通过实现ApplicationContext接口结合单例模式解决

1. 定义工具类实现ApplicationContext

package com.yuan.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * 通过ApplicationContext对象获取相关信息
 */
@Component
public class SpringContextUtils implements ApplicationContextAware {
    /**
     * 上下文对象实例
     */
    private static ApplicationContext applicationContext;

    /**
     * 获取applicationContext
     *
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 通过name获取 Bean.
     *
     * @param name
     * @return
     */
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过class获取Bean.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    /**
     * 通过name,以及Clazz获取Bean
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

2. ProductServiceNoCircleBySingleton

package com.yuan.service;

import com.yuan.utils.SpringContextUtils;
import org.springframework.stereotype.Service;

@Service
public class ProductServiceNoCircleBySingleton {
    /**
     * 通过单例模式结合ApplicationContext.getBen()解决循环依赖问题
     *
     * @return
     */
    private ProductServiceNoCircleBySingleton productServiceNoCircleBySingleton() {
        return SpringContextUtils.getBean(ProductServiceNoCircleBySingleton.class);
    }
}

3. OrderServiceNoCircleBySingleton

package com.yuan.service;

import com.yuan.utils.SpringContextUtils;
import org.springframework.stereotype.Service;

@Service
public class OrderServiceNoCircleBySingleton {
    /**
     * 通过单例模式结合ApplicationContext.getBen()解决循环依赖问题
     *
     * @return
     */
    private OrderServiceNoCircleBySingleton orderServiceNoCircleBySingleton() {
        return SpringContextUtils.getBean(OrderServiceNoCircleBySingleton.class);
    }
}

3. 通过懒加载方式解决

通过属性注入及加上@Lazy注解方式解决

1. ProductServiceNoCircleByLazy

package com.yuan.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

@Service
public class ProductServiceNoCircleByLazy {
    @Autowired
    @Lazy
    private OrderServiceNoCircleByLazy orderServiceNoCircleByLazy;
}

3. OrderServiceNoCircleByLazy

package com.yuan.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

@Service
public class OrderServiceNoCircleByLazy {
    @Autowired
    @Lazy
    private ProductServiceNoCircleByLazy productServiceNoCircleByLazy;
}

4. 使用@PostConstruct注解

1.ProductServiceNoCircleByPostConstruct

package com.yuan.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;

@Service
public class ProductServiceNoCircleByPostConstruct {
    @Autowired
    private OrderServiceNoCircleByPostConstruct orderServiceNoCircleByPostConstruct;

    @PostConstruct
    public void init() {
        orderServiceNoCircleByPostConstruct.setProductServiceNoCircleByPostConstruct(this);
    }

    public OrderServiceNoCircleByPostConstruct getOrderServiceNoCircleByPostConstruct() {
        return orderServiceNoCircleByPostConstruct;
    }
}

2. OrderServiceNoCircleByPostConstruct

package com.yuan.service;

import org.springframework.stereotype.Service;

@Service
public class OrderServiceNoCircleByPostConstruct {
    private ProductServiceNoCircleByPostConstruct productServiceNoCircleByPostConstruct;

    public void setProductServiceNoCircleByPostConstruct(ProductServiceNoCircleByPostConstruct productServiceNoCircleByPostConstruct) {
        this.productServiceNoCircleByPostConstruct = productServiceNoCircleByPostConstruct;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值