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中两个类之间相互依赖
- 新建产品Service与订单Service
- 两个类之间通过属性注入彼此
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. 启动是两个类之间循环依赖了
- 启动控制台打印如下:
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
最好的方法是重新设计、重构代码,进行解耦,消除循环依赖,如果没有时间重构,可以使用下面的方法
- 最简单的方法在yaml或properties中配置
spring.main.allow-circular-references=true
- 懒加载(基于Xml的方式)_在你的配置文件中,在互相依赖的两个bean的任意一个加上lazy-init属性
- 懒加载(基于注解的方式)_在你注入bean时,在互相依赖的两个bean上加上@Lazy注解也可以
- 通过实现
ApplicationContextAware
接口,并结合单例模式,在私有构造方法中通过applicationContext.getBen()
方式- 同时实现
ApplicationContextAware
与InitializingBean
接口,具体查看https://www.baeldung.com/circular-dependencies-in-spring文档- 使用
@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;
}
}