Spring 處理循環(huán)依賴只使用二級(jí)緩存,可以嗎?
什么是循環(huán)依賴?
先說一下什么是循環(huán)依賴,Spring在初始化A的時(shí)候需要注入B,而初始化B的時(shí)候需要注入A,在Spring啟動(dòng)后這2個(gè)Bean都要被初始化完成。
Spring的循環(huán)依賴有4種場(chǎng)景:
- 構(gòu)造器的循環(huán)依賴(singleton,prototype)
- 屬性的循環(huán)依賴(singleton,prototype)
「spring目前只支持singleton類型的屬性循環(huán)依賴」
構(gòu)造器的循環(huán)依賴
- @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));
- }
- }
運(yùn)行ConstructorMain的main方法的時(shí)候會(huì)在第一行就報(bào)異常,說明Spring沒辦法初始化所有的Bean,即上面這種形式的循環(huán)依賴Spring無法解決。
「構(gòu)造器的循環(huán)依賴,可以在構(gòu)造函數(shù)中使用@Lazy注解延遲加載。在注入依賴時(shí),先注入代理對(duì)象,當(dāng)首次使用時(shí)再創(chuàng)建對(duì)象完成注入」
- @Autowired
- public ConstructorB(@Lazy ConstructorA constructorA) {
- this.constructorA = constructorA;
- }
因?yàn)槲覀冎饕P(guān)注屬性的循環(huán)依賴,構(gòu)造器的循環(huán)依賴就不做過多分析了。
屬性的循環(huán)依賴
先演示一下什么是屬性的循環(huán)依賴。
- @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容器正常啟動(dòng),運(yùn)行結(jié)果為true,想實(shí)現(xiàn)類似的功能并不難,我寫個(gè)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);
- }
- // 實(shí)例化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));
- }
- }
「在開始后面的內(nèi)容的時(shí)候,我們先明確2個(gè)概念」
實(shí)例化:調(diào)用構(gòu)造函數(shù)將對(duì)象創(chuàng)建出來 初始化:調(diào)用構(gòu)造函數(shù)將對(duì)象創(chuàng)建出來后,給對(duì)象的屬性也被賦值。
可以看到只用了一個(gè)map就實(shí)現(xiàn)了循環(huán)依賴的實(shí)現(xiàn),但這種實(shí)現(xiàn)有個(gè)小缺陷,singletonObjects中的類有可能只是完成了實(shí)例化,并沒有完成初始化。
而在spring中singletonObjects中的類都完成了初始化,因?yàn)槲覀內(nèi)卫鼴ean的時(shí)候都是從singletonObjects中取的,不可能讓我們獲取到?jīng)]有初始化完成的對(duì)象。
所以我們來寫第二個(gè)實(shí)現(xiàn),「用singletonObjects存初始化完成的對(duì)象,而用earlySingletonObjects暫存實(shí)例化完成的對(duì)象,等對(duì)象初始化完畢再將對(duì)象放入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);
- }
- // 實(shí)例化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));
- }
- }
現(xiàn)在的實(shí)現(xiàn)和spring保持一致了,并且只用了2級(jí)緩存。spring為什么搞第三個(gè)緩存呢?「第三個(gè)緩存主要和代理對(duì)象相關(guān)」
我還是把上面的例子改進(jìn)一下,改成用3級(jí)緩存的實(shí)現(xiàn):
- 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();
- }
- // 實(shí)例化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) {
- // 因?yàn)檫@個(gè)方法有可能被執(zhí)行2次,所以這里應(yīng)該有個(gè)判斷
- // 如果之前提前進(jìn)行過aop操作則直接返回,知道意思就行,不寫了哈
- // 需要aop的話則返回代理對(duì)象,否則返回傳入的對(duì)象
- 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));
- }
- }
「為什么要包裝一個(gè)ObjectFactory對(duì)象?」
如果創(chuàng)建的Bean有對(duì)應(yīng)的aop代理,那其他對(duì)象注入時(shí),注入的應(yīng)該是對(duì)應(yīng)的代理對(duì)象;「但是Spring無法提前知道這個(gè)對(duì)象是不是有循環(huán)依賴的情況」,而正常情況下(沒有循環(huán)依賴情況),Spring都是在對(duì)象初始化后才創(chuàng)建對(duì)應(yīng)的代理。這時(shí)候Spring有兩個(gè)選擇:
- 不管有沒有循環(huán)依賴,實(shí)例化后就直接創(chuàng)建好代理對(duì)象,并將代理對(duì)象放入緩存,出現(xiàn)循環(huán)依賴時(shí),其他對(duì)象直接就可以取到代理對(duì)象并注入(只需要2級(jí)緩存,singletonObjects和earlySingletonObjects即可)
- 「不提前創(chuàng)建好代理對(duì)象,在出現(xiàn)循環(huán)依賴被其他對(duì)象注入時(shí),才提前生成代理對(duì)象(此時(shí)只完成了實(shí)例化)。這樣在沒有循環(huán)依賴的情況下,Bean還是在初始化完成才生成代理對(duì)象」(需要3級(jí)緩存)
- 「所以到現(xiàn)在為止你知道3級(jí)緩存的作用了把,主要是為了正常情況下,代理對(duì)象能在初始化完成后生成,而不用提前生成」
緩存 | 說明 |
---|---|
singletonObjects | 第一級(jí)緩存,存放初始化完成的Bean |
earlySingletonObjects | 第二級(jí)緩存,存放實(shí)例化完成的Bean,有可能被進(jìn)行了代理 |
singletonFactories | 延遲生成代理對(duì)象 |
源碼解析
獲取Bean的時(shí)候先嘗試從3級(jí)緩存中獲取,和我們上面的Demo差不多哈!
DefaultSingletonBeanRegistry#getSingleton
當(dāng)從緩存中獲取不到時(shí),會(huì)進(jìn)行創(chuàng)建 AbstractAutowireCapableBeanFactory#doCreateBean(刪除了部分代碼哈)
發(fā)生循環(huán)依賴時(shí),會(huì)從工廠里獲取代理對(duì)象哈!
當(dāng)開啟aop代理時(shí),SmartInstantiationAwareBeanPostProcessor的一個(gè)實(shí)現(xiàn)類有AbstractAutoProxyCreator
AbstractAutoProxyCreator#getEarlyBeanReference
getEarlyBeanReference方法提前進(jìn)行代理,為了防止后面再次進(jìn)行代理,需要用earlyProxyReferences記錄一下,這個(gè)Bean已經(jīng)被代理過了,不用再代理了。
AbstractAutoProxyCreator#postProcessAfterInitialization
這個(gè)方法是進(jìn)行aop代理的地方,因?yàn)橛锌赡芴崆按砹?,所以先根?jù)earlyProxyReferences判斷一下,是否提前代理了,提前代理過就不用代理了。
當(dāng)bean初始化完畢,會(huì)放入一級(jí)緩存,并從二三級(jí)緩存刪除。
DefaultSingletonBeanRegistry#addSingleton
發(fā)生循環(huán)依賴時(shí),整體的執(zhí)行流程如下:
本文轉(zhuǎn)載自微信公眾號(hào)「Java識(shí)堂」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java識(shí)堂公眾號(hào)。