什么是循环依赖?
复现循环依赖现象
代码
@Component
public class BeanA {
@Autowired
BeanB beanB;
}
@Component
public class BeanB {
@Autowired
BeanA beanA;
}
@SpringBootApplication(nameGenerator = CustomBeanNameGenerator.class)
public class CircularDependenceApplication {
public static void main(String[] args) throws Throwable {
SpringApplication.run(CircularDependenceApplication.class, args);
}
}
ERROR LOG:
Connected to the target VM, address: '127.0.0.1:58131', transport: 'socket'
12:00:26.904 [Thread-0] DEBUG org.springframework.boot.devtools.restart.classloader.RestartClassLoader - Created RestartClassLoader org.springframework.boot.devtools.restart.classloader.RestartClassLoader@466cb152
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.2)
2022-08-25 12:00:27.217 INFO 24885 --- [ restartedMain] c.j.d.s.CircularDependenceApplication : Starting CircularDependenceApplication using Java 17.0.4 on JansoradeIntel-iMac.local with PID 24885 (/Users/jansora/Documents/Github/demo/backend/spring-boot/demo/target/classes started by jansora in /Users/jansora/Documents/Github/demo)
2022-08-25 12:00:27.218 DEBUG 24885 --- [ restartedMain] c.j.d.s.CircularDependenceApplication : Running with Spring Boot v2.7.2, Spring v5.3.22
2022-08-25 12:00:27.218 INFO 24885 --- [ restartedMain] c.j.d.s.CircularDependenceApplication : No active profile set, falling back to 1 default profile: "default"
2022-08-25 12:00:27.246 INFO 24885 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2022-08-25 12:00:27.870 WARN 24885 --- [ restartedMain] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.jansora.demo.spring.lib.BeanA': Unsatisfied dependency expressed through field 'beanB'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.jansora.demo.spring.lib.BeanB': Unsatisfied dependency expressed through field 'beanA'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'com.jansora.demo.spring.lib.BeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
2022-08-25 12:00:27.880 INFO 24885 --- [ restartedMain] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-08-25 12:00:27.892 ERROR 24885 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| com.jansora.demo.spring.lib.BeanA (field com.jansora.demo.spring.lib.BeanB com.jansora.demo.spring.lib.BeanA.beanB)
↑ ↓
| com.jansora.demo.spring.lib.BeanB (field com.jansora.demo.spring.lib.BeanA com.jansora.demo.spring.lib.BeanB.beanA)
└─────┘
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:58131', transport: 'socket'
Process finished with exit code 0
如何解决循环依赖?
通过三级缓存可以解决, 先看下获取单例 bean 的代码
核心代码在 : org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
// ... 其他内容 ... //
/** Cache of singleton objects: bean name to bean instance. */
// 第一级缓存:用于保存实例化、注入、初始化完成的 bean 实例;
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
// 第二级缓存:用于保存实例化完成的 bean 实例;
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
// 第三级缓存:singletonFactories,用于保存 bean 创建工厂,以便后面有机会创建代理对象。
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// ... 其他内容 ... //
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
// 从第一级缓存取 bean, 取到直接返回
Object singletonObject = this.singletonObjects.get(beanName);
// 如果 bean 在创建, 从第二级缓存取
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 lock1
// 再次确认一级缓存是否已被加载
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;
}
// ... 其他内容 ... //
}
为什么要有 3 级缓存 ?
我们先说“一级缓存”的作用,变量命名为 singletonObjects,结构是 Map<String, Object>,它就是一个单例池,将初始化好的对象放到里面,给其它线程使用,如果没有第一级缓存,程序不能保证 Spring 的单例属性。
“二级缓存”先放放,我们直接看“三级缓存”的作用,变量命名为 singletonFactories,结构是 Map<String, ObjectFactory<?>>,Map 的 Value 是一个对象的代理工厂,所以“三级缓存”的作用,其实就是用来存放对象的代理工厂。
那这个对象的代理工厂有什么作用呢,我先给出答案,它的主要作用是存放半成品的单例 Bean,目的是为了“打破循环”,可能大家还是不太懂,这里我再稍微解释一下。
我们回到文章开头的例子,创建 A 对象时,会把实例化的 A 对象存入“三级缓存”,这个 A 其实是个半成品,因为没有完成 A 的依赖属性 B 的注入,所以后面当初始化 B 时,B 又要去找 A,这时就需要从“三级缓存”中拿到这个半成品的 A(这里描述,其实也不完全准确,因为不是直接拿,为了让大家好理解,我就先这样描述),打破循环。
那我再问一个问题,为什么“三级缓存”不直接存半成品的 A,而是要存一个代理工厂呢 ?答案是因为 AOP。
那“二级缓存”的作用就清楚了,就是用来存放对象工厂生成的对象,这个对象可能是原对象,也可能是个代理对象。
能干掉第 2 级缓存么 ?
假如 A 需要进行 AOP,因为代理对象每次都是生成不同的对象,如果干掉第二级缓存,只有第一、三级缓存:
B 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A1。
C 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A2。
看到问题没?你通过 A 的工厂的代理对象,生成了两个不同的对象 A1 和 A2,所以为了避免这种问题的出现,我们搞个二级缓存,把 A1 存下来,下次再获取时,直接从二级缓存获取,无需再生成新的代理对象。
所以“二级缓存”的目的是为了避免因为 AOP 创建多个对象,其中存储的是半成品的 AOP 的单例 bean。
如果没有 AOP 的话,我们其实只要 1、3 级缓存,就可以满足要求。