什么是循環依賴?
先說一下什么是循環依賴,Spring在初始化A的時候需要注入B,而初始化B的時候需要注入A,在Spring啟動后這2個Bean都要被初始化完成。
Spring的循環依賴有4種場景:
- 構造器的循環依賴(singleton,prototype)
- 屬性的循環依賴(singleton,prototype)
「spring目前只支持singleton類型的屬性循環依賴」
構造器的循環依賴
- @Component
- public class ConstructorA {
- private ConstructorB constructorB;
- @Autowired
- public ConstructorA(ConstructorB constructorB) {
- this.constructorB = constructorB;
- }
- }
- @Component
- public class ConstructorB {
- private ConstructorA constructorA;
- @Autowired
- public ConstructorB(ConstructorA constructorA) {
- this.constructorA = constructorA;
- }
- }
- @Configuration
- @ComponentScan("com.javashitang.dependency.constructor")
- public class ConstructorConfig {
- }
- public class ConstructorMain {
- public static void main(String[] args) {
- AnnotationConfigApplicationContext context =
- new AnnotationConfigApplicationContext(ConstructorConfig.class);
- System.out.println(context.getBean(ConstructorA.class));
- System.out.println(context.getBean(ConstructorB.class));
- }
- }
運行ConstructorMain的main方法的時候會在第一行就報異常,說明Spring沒辦法初始化所有的Bean,即上面這種形式的循環依賴Spring無法解決。
「構造器的循環依賴,可以在構造函數中使用@Lazy注解延遲加載。在注入依賴時,先注入代理對象,當首次使用時再創建對象完成注入」
- @Autowired
- public ConstructorB(@Lazy ConstructorA constructorA) {
- this.constructorA = constructorA;
- }
因為我們主要關注屬性的循環依賴,構造器的循環依賴就不做過多分析了。
屬性的循環依賴
先演示一下什么是屬性的循環依賴。
- @Data
- @Component
- public class A {
- @Autowired
- private B b;
- }
- @Data
- @Component
- public class B {
- @Autowired
- private A a;
- }
- @Configuration
- @EnableAspectJAutoProxy
- @ComponentScan("com.javashitang.dependency")
- public class Config {
- }
- public class Main {
- public static void main(String[] args) {
- AnnotationConfigApplicationContext context =
- new AnnotationConfigApplicationContext(Config.class);
- System.out.println(context.getBean(A.class).getB() == context.getBean(B.class));
- System.out.println(context.getBean(B.class).getA() == context.getBean(A.class));
- }
- }
Spring容器正常啟動,運行結果為true,想實現類似的功能并不難,我寫個demo演示一下。
- public class DependencyDemoV1 {
- private static final Map<String, Object> singletonObjects =
- new HashMap<>(256);
- @SneakyThrows
- public static <T> T getBean(Class<T> beanClass) {
- String beanName = beanClass.getSimpleName();
- if (singletonObjects.containsKey(beanName)) {
- return (T) singletonObjects.get(beanName);
- }
- // 實例化bean
- Object object = beanClass.getDeclaredConstructor().newInstance();
- singletonObjects.put(beanName, object);
- // 開始初始化bean,即填充屬性
- Field[] fields = object.getClass().getDeclaredFields();
- for (Field field : fields) {
- field.setAccessible(true);
- // 獲取需要注入字段的class
- Class<?> fieldClass = field.getType();
- field.set(object, getBean(fieldClass));
- }
- return (T) object;
- }
- public static void main(String[] args) {
- // 假裝掃描出來的類
- Class[] classes = {A.class, B.class};
- for (Class aClass : classes) {
- getBean(aClass);
- }
- System.out.println(getBean(A.class).getB() == getBean(B.class));
- System.out.println(getBean(B.class).getA() == getBean(A.class));
- }
- }
「在開始后面的內容的時候,我們先明確2個概念」
實例化:調用構造函數將對象創建出來 初始化:調用構造函數將對象創建出來后,給對象的屬性也被賦值。
可以看到只用了一個map就實現了循環依賴的實現,但這種實現有個小缺陷,singletonObjects中的類有可能只是完成了實例化,并沒有完成初始化。
而在spring中singletonObjects中的類都完成了初始化,因為我們取單例Bean的時候都是從singletonObjects中取的,不可能讓我們獲取到沒有初始化完成的對象。
所以我們來寫第二個實現,「用singletonObjects存初始化完成的對象,而用earlySingletonObjects暫存實例化完成的對象,等對象初始化完畢再將對象放入singletonObjects,并從earlySingletonObjects刪除」。
- public class DependencyDemoV2 {
- private static final Map<String, Object> singletonObjects =
- new HashMap<>(256);
- private static final Map<String, Object> earlySingletonObjects =
- new HashMap<>(256);
- @SneakyThrows
- public static <T> T getBean(Class<T> beanClass) {
- String beanName = beanClass.getSimpleName();
- if (singletonObjects.containsKey(beanName)) {
- return (T) singletonObjects.get(beanName);
- }
- if (earlySingletonObjects.containsKey(beanName)) {
- return (T) earlySingletonObjects.get(beanName);
- }
- // 實例化bean
- Object object = beanClass.getDeclaredConstructor().newInstance();
- earlySingletonObjects.put(beanName, object);
- // 開始初始化bean,即填充屬性
- Field[] fields = object.getClass().getDeclaredFields();
- for (Field field : fields) {
- field.setAccessible(true);
- // 獲取需要注入字段的class
- Class<?> fieldClass = field.getType();
- field.set(object, getBean(fieldClass));
- }
- singletonObjects.put(beanName, object);
- earlySingletonObjects.remove(beanName);
- return (T) object;
- }
- public static void main(String[] args) {
- // 假裝掃描出來的類
- Class[] classes = {A.class, B.class};
- for (Class aClass : classes) {
- getBean(aClass);
- }
- System.out.println(getBean(A.class).getB() == getBean(B.class));
- System.out.println(getBean(B.class).getA() == getBean(A.class));
- }
- }
現在的實現和spring保持一致了,并且只用了2級緩存。spring為什么搞第三個緩存呢?「第三個緩存主要和代理對象相關」
我還是把上面的例子改進一下,改成用3級緩存的實現:
- public interface ObjectFactory<T> {
- T getObject();
- }
- public class DependencyDemoV3 {
- private static final Map<String, Object> singletonObjects =
- new HashMap<>(256);
- private static final Map<String, Object> earlySingletonObjects =
- new HashMap<>(256);
- private static final Map<String, ObjectFactory<?>> singletonFactories =
- new HashMap<>(256);
- @SneakyThrows
- public static <T> T getBean(Class<T> beanClass) {
- String beanName = beanClass.getSimpleName();
- if (singletonObjects.containsKey(beanName)) {
- return (T) singletonObjects.get(beanName);
- }
- if (earlySingletonObjects.containsKey(beanName)) {
- return (T) earlySingletonObjects.get(beanName);
- }
- ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
- if (singletonFactory != null) {
- return (T) singletonFactory.getObject();
- }
- // 實例化bean
- Object object = beanClass.getDeclaredConstructor().newInstance();
- singletonFactories.put(beanName, () -> {
- Object proxy = createProxy(object);
- singletonFactories.remove(beanName);
- earlySingletonObjects.put(beanName, proxy);
- return proxy;
- });
- // 開始初始化bean,即填充屬性
- Field[] fields = object.getClass().getDeclaredFields();
- for (Field field : fields) {
- field.setAccessible(true);
- // 獲取需要注入字段的class
- Class<?> fieldClass = field.getType();
- field.set(object, getBean(fieldClass));
- }
- createProxy(object);
- singletonObjects.put(beanName, object);
- singletonFactories.remove(beanName);
- earlySingletonObjects.remove(beanName);
- return (T) object;
- }
- public static Object createProxy(Object object) {
- // 因為這個方法有可能被執行2次,所以這里應該有個判斷
- // 如果之前提前進行過aop操作則直接返回,知道意思就行,不寫了哈
- // 需要aop的話則返回代理對象,否則返回傳入的對象
- return object;
- }
- public static void main(String[] args) {
- // 假裝掃描出來的類
- Class[] classes = {A.class, B.class};
- for (Class aClass : classes) {
- getBean(aClass);
- }
- System.out.println(getBean(A.class).getB() == getBean(B.class));
- System.out.println(getBean(B.class).getA() == getBean(A.class));
- }
- }
「為什么要包裝一個ObjectFactory對象?」
如果創建的Bean有對應的aop代理,那其他對象注入時,注入的應該是對應的代理對象;「但是Spring無法提前知道這個對象是不是有循環依賴的情況」,而正常情況下(沒有循環依賴情況),Spring都是在對象初始化后才創建對應的代理。這時候Spring有兩個選擇:
- 不管有沒有循環依賴,實例化后就直接創建好代理對象,并將代理對象放入緩存,出現循環依賴時,其他對象直接就可以取到代理對象并注入(只需要2級緩存,singletonObjects和earlySingletonObjects即可)
- 「不提前創建好代理對象,在出現循環依賴被其他對象注入時,才提前生成代理對象(此時只完成了實例化)。這樣在沒有循環依賴的情況下,Bean還是在初始化完成才生成代理對象」(需要3級緩存)
- 「所以到現在為止你知道3級緩存的作用了把,主要是為了正常情況下,代理對象能在初始化完成后生成,而不用提前生成」
緩存 | 說明 |
---|---|
singletonObjects | 第一級緩存,存放初始化完成的Bean |
earlySingletonObjects | 第二級緩存,存放實例化完成的Bean,有可能被進行了代理 |
singletonFactories | 延遲生成代理對象 |
源碼解析
獲取Bean的時候先嘗試從3級緩存中獲取,和我們上面的Demo差不多哈!
DefaultSingletonBeanRegistry#getSingleton
當從緩存中獲取不到時,會進行創建 AbstractAutowireCapableBeanFactory#doCreateBean(刪除了部分代碼哈)
發生循環依賴時,會從工廠里獲取代理對象哈!
當開啟aop代理時,SmartInstantiationAwareBeanPostProcessor的一個實現類有AbstractAutoProxyCreator
AbstractAutoProxyCreator#getEarlyBeanReference
getEarlyBeanReference方法提前進行代理,為了防止后面再次進行代理,需要用earlyProxyReferences記錄一下,這個Bean已經被代理過了,不用再代理了。
AbstractAutoProxyCreator#postProcessAfterInitialization
這個方法是進行aop代理的地方,因為有可能提前代理了,所以先根據earlyProxyReferences判斷一下,是否提前代理了,提前代理過就不用代理了。
當bean初始化完畢,會放入一級緩存,并從二三級緩存刪除。
DefaultSingletonBeanRegistry#addSingleton
發生循環依賴時,整體的執行流程如下:
原文鏈接:https://mp.weixin.qq.com/s/WfY0jT2yukXgJ0pOkK6RuA