Spring ⑦ 循环引用问题
Spring 源码系列文章会遵循由浅入深,由易到难,由宏观到微观的原则,目标是尽量降低学习难度,而不是一上来就迷失在源码当中. 文章会从一个场景作为出发点,针对性的目的性极强的针对该场景对 Spring 的实现原理,源码进行探究学习。该系列文章会让你收获什么? 从对 Spring 的使用者成为 Spring 专家。该文章会同步在微信公众号 【DevXJava】, 方便在微信客户端阅读。
Bean 的实例化初始化顺序
场景
public class CircularReferenceExp1 {
public static void main(String[] args) {
// 循环引用实验
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
processor.setBeanFactory(factory);
factory.addBeanPostProcessor(processor);
factory.registerBeanDefinition("bean1" , BeanDefinitionBuilder
.genericBeanDefinition(Bean1.class)
.getBeanDefinition());
factory.registerBeanDefinition("bean2" , BeanDefinitionBuilder
.genericBeanDefinition(Bean2.class)
.getBeanDefinition());
factory.registerBeanDefinition("bean3" , BeanDefinitionBuilder
.genericBeanDefinition(Bean3.class)
.getBeanDefinition());
Bean3 bean3 = factory.getBean(Bean3.class);
System.out.println("bean1 -> " + bean3);
}
static class Bean1 {
@Autowired
Bean3 bean3;
public Bean1() {
System.out.println("======================== bean1 实例化");
}
}
static class Bean2 {
@Autowired
Bean1 bean1;
public Bean2() {
System.out.println("++++++++++++++++++++++++++ bean2 实例化");
}
}
static class Bean3 {
@Autowired
Bean2 bean2;
public Bean3() {
System.out.println("++++++++++++++++++++++++++ Bean3 实例化");
}
}
}
运行日志输出:
20:56:41.417 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean3'
++++++++++++++++++++++++++ Bean3 实例化
20:56:41.476 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
++++++++++++++++++++++++++ bean2 实例化
20:56:41.477 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean1'
======================== bean1 实例化
bean1 -> org.devx.spring.certified.professional.edu.hp.experiment.CircularReferenceExp1$Bean3@22635ba0
spring
在创建 Bean 的过程中是从外到内的去创建 Bean 。在解析 Bean 中依赖项的时候便会去实例化该依赖项对应的 Bean 并完成 spring 对 Bean 的初始化发布过程。
构造器依赖注入时循环引用
先说结论构造器依赖注入时存在循环引用的情况下会导致 Bean 创建失败抛出异常. 要想知道原因只要理解了构造器依赖注入与属性、方法依赖注入的区别是什么就很容易理解了。构造器依赖注入时需要先找到依赖项才能实例化当前 Bean ,属性和方法依赖注入时是先实例化当前 Bean 再去寻找依赖项实例。
场景
public class CircularReferenceExp2 {
public static void main(String[] args) {
// 循环引用实验
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
processor.setBeanFactory(factory);
factory.addBeanPostProcessor(processor);
factory.registerBeanDefinition("bean1" , BeanDefinitionBuilder
.genericBeanDefinition(Bean1.class)
.getBeanDefinition());
factory.registerBeanDefinition("bean2" , BeanDefinitionBuilder
.genericBeanDefinition(Bean2.class)
.getBeanDefinition());
Bean1 bean1 = factory.getBean(Bean1.class);
System.out.println("bean1 -> " + bean1);
}
static class Bean1 {
final Bean2 bean2;
public Bean1(Bean2 bean2) {
this.bean2 = bean2;
System.out.println("======================== bean1 实例化 , bean2 is = " + bean2);
}
}
static class Bean2 {
final Bean1 bean1;
public Bean2(Bean1 bean1) {
this.bean1 = bean1;
System.out.println("++++++++++++++++++++++++++ bean2 实例化 , bean1 is = " + bean1);
}
}
}
观察输出的日志发现,输出了两次
Creating shared instance of singleton bean 'bean1'
, 在创建bean2
的过程中因为工厂内还没有bean1
所以又去创建了一次. 在第二次创建bean1
的过程中,spring
发现bean1
其实正在被创建的过程中(首次创建bean1
的过程), 所以spring
抛出了BeanCurrentlyInCreationException
异常。 假设如果spring
允许第二次创建bean1
成功的话, 那么首次创建bean1
的过程也可以成功.这样工厂中就会存在两个bean1
实例,或者是只存在一个其中一个被覆盖,那么bean2
中究竟应该持有那个bean1
的实例呢?问题变得更为复杂了.
运行日志输出:
21:23:06.516 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean1'
21:26:01.837 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
21:29:03.383 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean1'
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bean1': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bean2': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean1': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:233)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1282)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1243)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:494)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.devx.spring.certified.professional.edu.hp.experiment.CircularReferenceExp2.main(CircularReferenceExp2.java:32)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bean2': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean1': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1389)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 15 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean1': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1389)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 29 more
属性方法依赖注入时循环引用
在前面内容中已经介绍了属性、方法注入与构造器注入时循环引用问题的区别,接下来重点讲解在属性、方法注入时
spring
如何处理循环引用问题。
场景
public class CircularReferenceExp3 {
public static void main(String[] args) {
// 循环引用实验
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
Logger logger = loggerContext.getLogger(DefaultListableBeanFactory.class);
logger.setLevel(Level.TRACE);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
processor.setBeanFactory(factory);
factory.addBeanPostProcessor(processor);
factory.registerBeanDefinition("bean1" , BeanDefinitionBuilder
.genericBeanDefinition(Bean1.class)
.getBeanDefinition());
factory.registerBeanDefinition("bean2" , BeanDefinitionBuilder
.genericBeanDefinition(Bean2.class)
.getBeanDefinition());
Bean1 bean1 = factory.getBean(Bean1.class);
System.out.println("bean1 -> " + bean1);
}
static class Bean1 {
@Autowired
Bean2 bean2;
public Bean1() {
System.out.println("======================== bean1 实例化 ");
}
}
static class Bean2 {
@Autowired
Bean1 bean1;
public Bean2() {
System.out.println("++++++++++++++++++++++++++ bean2 实例化 ");
}
}
}
运行日志输出:
从输入的日志中可以观察到在创建了实列对象 (
Creating shared instance
) 后,该实例就被添加到了缓存当中 (Eagerly caching bean
) . 目的就是为了解决可能存在的循环引用场景。而bean2
在注入bena1
的时候获取的bean1
是从缓存中获取的实例对象 (Returning eagerly cached instance of singleton bean
).
17:34:34.328 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean1'
17:34:34.332 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'bean1'
======================== bean1 实例化
17:34:34.385 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'bean1' to allow for resolving potential circular references
17:34:34.390 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
17:34:34.390 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'bean2'
++++++++++++++++++++++++++ bean2 实例化
17:34:34.391 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'bean2' to allow for resolving potential circular references
17:34:34.391 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning eagerly cached instance of singleton bean 'bean1' that is not fully initialized yet - a consequence of a circular reference
17:34:34.393 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'bean2'
17:34:34.393 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'bean1'
bean1 -> org.devx.spring.certified.professional.edu.hp.experiment.CircularReferenceExp3$Bean1@22635ba0
从图中分析出在循环引用时注入的依赖 bean 是刚刚完成创建过程的 bean 并没有完成整体的初始化过程.因为在没有执行
populateBean
方法和initializeBean
就被放入了singletonFactories
缓存当中.也就是说这时候注入的 bean 实例并没有经过BeanPostProcessor
的处理, 也没有执行InitializingBean
的afterPropertiesSet
方法. 其实这时候被放入singletonFactories
缓存中的是一个ObjectFactory
的匿名内部类其中指向了AbstractAutowireCapableBeanFactory#etEarlyBeanReference
方法。其中会去调用SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
方法, 比如AnnotationAwareAspectJAutoProxyCreator
就保证了我们获取到的是一个代理对象。下面会附上一些关键部分源码帮助读者理解图示.
getSingleton
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
doCreateBean
addSingletonFactory
/**
* Add the given singleton factory for building the specified singleton
* if necessary.
* <p>To be called for eager registration of singletons, e.g. to be able to
* resolve circular references.
* @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object
*/
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
getEarlyBeanReference
/**
* Obtain a reference for early access to the specified bean,
* typically for the purpose of resolving a circular reference.
* @param beanName the name of the bean (for error handling purposes)
* @param mbd the merged bean definition for the bean
* @param bean the raw bean instance
* @return the object to expose as bean reference
*/
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
addSingleton
在 bean 完成了整个创建和初始化过程后 , bean 会被加入到
singletonObjects
缓存中,并从singletonFactories
、earlySingletonObjects
缓存中清除.
/**
* Add the given singleton object to the singleton cache of this factory.
* <p>To be called for eager registration of singletons.
* @param beanName the name of the bean
* @param singletonObject the singleton object
*/
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
到这里 spring
是如何处理循环引用的原理我们就讲完了,接下来看如果在使用过程中出现了循环引用该如何解决. (最好的办法就是不要循环引用,这个就涉及到项目结构设计,编码规范等等方面,这里并不展开。).
解决循环引用
解决循环引用的思路是
延迟加载
让 bean 在真正被使用时再去加载,这样做的缺点是会有一点性能损耗一般情况下可以忽略不计. 可以通过使用@Lazy
注解的方式或者注入ObjectFactory
或ObjectProvider
或jsr-330
Provider
的方式实现懒加载.
总结
本章讲解了 构造器依赖注入时循环引用
和 属性方法依赖注入时循环引用
的场景。在构造器注入时得到的依赖项是经过了完整的初始化过程的,可以说是得到的一个完整的 bean 。而属性或方法注入的场景得到的 bean 是没有经过完全初始化的,所以在使用 bean 时需要警惕.
抱歉最近没有更新因为我在追剧,我最近在考虑要不要转行卖鱼了 : )
DevX
会持续分享 Java
技术干货,如果你觉得本文对你有帮助希望你可以分享给更多的朋友看到。该文章会同步在微信公众号 【 DevXJava
】, 方便在微信客户端阅读。