spring循环依赖问题
实际业务系统中由于业务的复杂性,可能会导致业务层组件产生服务A依赖服务B,服务B依赖服务C,服务C依赖服务A的循环依赖问题。有时候产生了这种问题可以服务正常运行,有时候spring会提示依赖错误。那为何会有此区别呢?来看下代码
测试代码一如下:
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class ServiceA {
private ServiceB serviceB;
}
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class ServiceB {
private ServiceC serviceC;
}
启动日志如下:
2019-10-13 16:58:48.221 INFO 12528 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication on jingjing with PID 12528 (D:\workspace\demo\target\classes started by jingy in D:\workspace\demo)
2019-10-13 16:58:48.224 INFO 12528 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2019-10-13 16:58:49.580 INFO 12528 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2019-10-13 16:58:49.604 INFO 12528 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-10-13 16:58:49.604 INFO 12528 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.26]
2019-10-13 16:58:49.712 INFO 12528 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-10-13 16:58:49.712 INFO 12528 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1407 ms
2019-10-13 16:58:49.767 WARN 12528 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'serviceA' defined in file [D:\workspace\demo\target\classes\com\example\demo\service\ServiceA.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'serviceB' defined in file [D:\workspace\demo\target\classes\com\example\demo\service\ServiceB.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'serviceC' defined in file [D:\workspace\demo\target\classes\com\example\demo\service\ServiceC.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'serviceA': Requested bean is currently in creation: Is there an unresolvable circular reference?
2019-10-13 16:58:49.770 INFO 12528 --- [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2019-10-13 16:58:49.783 INFO 12528 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-10-13 16:58:49.789 ERROR 12528 --- [ 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:
┌─────┐
| serviceA defined in file [D:\workspace\demo\target\classes\com\example\demo\service\ServiceA.class]
↑ ↓
| serviceB defined in file [D:\workspace\demo\target\classes\com\example\demo\service\ServiceB.class]
↑ ↓
| serviceC defined in file [D:\workspace\demo\target\classes\com\example\demo\service\ServiceC.class]
└─────┘
spring提示存在循环依赖,容器启动失败
测试代码二如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ServiceB {
@Autowired
private ServiceC serviceC;
}
@Service
public class ServiceC {
@Autowired
private ServiceA serviceA;
}
启动日志如下:
2019-10-13 17:01:36.673 INFO 14232 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication on jingjing with PID 14232 (D:\workspace\demo\target\classes started by jingy in D:\workspace\demo)
2019-10-13 17:01:36.677 INFO 14232 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2019-10-13 17:01:38.274 INFO 14232 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2019-10-13 17:01:38.315 INFO 14232 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-10-13 17:01:38.316 INFO 14232 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.26]
2019-10-13 17:01:38.431 INFO 14232 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-10-13 17:01:38.431 INFO 14232 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1629 ms
2019-10-13 17:01:38.712 INFO 14232 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-10-13 17:01:38.919 INFO 14232 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-10-13 17:01:38.925 INFO 14232 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 2.871 seconds (JVM running for 4.737)
服务启动正常
两种方式都产生了循环依赖的条件,只是注入方式不同,第一种方式采用构造器注入,第二种通过类型注入。那么spring是怎么处理这两种注入方式产生的循环依赖问题呢?
springboot的入口方法是SpringApplication类的run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
跟踪代码查看这个方法里面主要干了以下几件事
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//准备上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//这里会扫描启动类路径下的所有需要加载的组件,转化为元信息,存储在DefaultListableBeanFactory中,这一步很重要
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
...省略部分代码...
return context;
}
spring实际创建bean的方法为AbstractAutowireCapableBeanFactory类的doCreateBean
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
这里的createBeanInstance方法其实是调用了bean的默认构造器进行了实例化,假如bean的依赖成员从构造器注入的话,这里是没办法进行依赖的实例化的。
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//这里实际是bean初始化前先缓存了创建bean的ObjectFactory,这个时候bean其实已经调用默认构造方法通过反射进行了实例化
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//这里对属性进行了填充
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
由此可见,spring对bean的创建过程可谓有着精心的设计,而我们使用spring所会碰到的问题,spring早已有预见提供了解决方案。如果理解有误的地方,还请不吝指正。学习之路无止境,学习好的代码设计能让我们不断提升自己,诸君共勉。