曹工說Spring Boot源碼(29)-- Spring 解決循環依賴為什么使用三級緩存,而不是二級緩存


寫在前面的話

相關背景及資源:

曹工說Spring Boot源碼(1)-- Bean Definition到底是什么,附spring思維導圖分享

曹工說Spring Boot源碼(2)-- Bean Definition到底是什么,咱們對着接口,逐個方法講解

曹工說Spring Boot源碼(3)-- 手動注冊Bean Definition不比游戲好玩嗎,我們來試一下

曹工說Spring Boot源碼(4)-- 我是怎么自定義ApplicationContext,從json文件讀取bean definition的?

曹工說Spring Boot源碼(5)-- 怎么從properties文件讀取bean

曹工說Spring Boot源碼(6)-- Spring怎么從xml文件里解析bean的

曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中得到了什么(上)

曹工說Spring Boot源碼(8)-- Spring解析xml文件,到底從中得到了什么(util命名空間)

曹工說Spring Boot源碼(9)-- Spring解析xml文件,到底從中得到了什么(context命名空間上)

曹工說Spring Boot源碼(10)-- Spring解析xml文件,到底從中得到了什么(context:annotation-config 解析)

曹工說Spring Boot源碼(11)-- context:component-scan,你真的會用嗎(這次來說說它的奇技淫巧)

曹工說Spring Boot源碼(12)-- Spring解析xml文件,到底從中得到了什么(context:component-scan完整解析)

曹工說Spring Boot源碼(13)-- AspectJ的運行時織入(Load-Time-Weaving),基本內容是講清楚了(附源碼)

曹工說Spring Boot源碼(14)-- AspectJ的Load-Time-Weaving的兩種實現方式細細講解,以及怎么和Spring Instrumentation集成

曹工說Spring Boot源碼(15)-- Spring從xml文件里到底得到了什么(context:load-time-weaver 完整解析)

曹工說Spring Boot源碼(16)-- Spring從xml文件里到底得到了什么(aop:config完整解析【上】)

曹工說Spring Boot源碼(17)-- Spring從xml文件里到底得到了什么(aop:config完整解析【中】)

曹工說Spring Boot源碼(18)-- Spring AOP源碼分析三部曲,終於快講完了 (aop:config完整解析【下】)

曹工說Spring Boot源碼(19)-- Spring 帶給我們的工具利器,創建代理不用愁(ProxyFactory)

曹工說Spring Boot源碼(20)-- 碼網恢恢,疏而不漏,如何記錄Spring RedisTemplate每次操作日志

曹工說Spring Boot源碼(21)-- 為了讓大家理解Spring Aop利器ProxyFactory,我已經拼了

曹工說Spring Boot源碼(22)-- 你說我Spring Aop依賴AspectJ,我依賴它什么了

曹工說Spring Boot源碼(23)-- ASM又立功了,Spring原來是這么遞歸獲取注解的元注解的

曹工說Spring Boot源碼(24)-- Spring注解掃描的瑞士軍刀,asm技術實戰(上)

曹工說Spring Boot源碼(25)-- Spring注解掃描的瑞士軍刀,ASM + Java Instrumentation,順便提提Jar包破解

曹工說Spring Boot源碼(26)-- 學習字節碼也太難了,實在不能忍受了,寫了個小小的字節碼執行引擎

曹工說Spring Boot源碼(27)-- Spring的component-scan,光是include-filter屬性的各種配置方式,就夠玩半天了

曹工說Spring Boot源碼(28)-- Spring的component-scan機制,讓你自己來進行簡單實現,怎么辦

工程代碼地址 思維導圖地址

工程結構圖:

什么是三級緩存

在獲取單例bean的時候,會進入以下方法:

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
    
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
                // 1
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
                                // 2
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
                                        // 3
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
                                                // 4
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

這里面涉及到了該類中的三個field。

	/** 1級緩存 Cache of singleton objects: bean name to bean instance. */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** 2級緩存 Cache of early singleton objects: bean name to bean instance. */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

	/** 3級緩存 Cache of singleton factories: bean name to ObjectFactory. */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

接着說前面的代碼。

  • 1處,在最上層的緩存singletonObjects中,獲取單例bean,這里面拿到的bean,直接可以使用;如果沒取到,則進入2處

  • 2處,在2級緩存earlySingletonObjects中,查找bean;

  • 3處,如果在2級緩存中,還是沒找到,則在3級緩存中查找對應的工廠對象,利用拿到的工廠對象(工廠對象中,有3個field,一個是beanName,一個是RootBeanDefinition,一個是已經創建好的,但還沒有注入屬性的bean),去獲取包裝后的bean,或者說,代理后的bean。

    什么是已經創建好的,但沒有注入屬性的bean?

    比如一個bean,有10個字段,你new了之后,對象已經有了,內存空間已經開辟了,堆里已經分配了該對象的空間了,只是此時的10個field還是null。

ioc容器,普通循環依賴,一級緩存夠用嗎

說實話,如果簡單寫寫的話,一級緩存都沒問題。給大家看一個我以前寫的渣渣ioc容器:

曹工說Tomcat4:利用 Digester 手擼一個輕量的 Spring IOC容器

@Data
public class BeanDefinitionRegistry {
    /**
     * map:存儲 bean的class-》bean實例
     */
    private Map<Class, Object> beanMapByClass = new ConcurrentHashMap<>();
    
    /**
     * 根據bean 定義獲取bean
     * 1、先查bean容器,查到則返回
     * 2、生成bean,放進容器(此時,依賴還沒注入,主要是解決循環依賴問題)
     * 3、注入依賴
     *
     * @param beanDefiniton
     * @return
     */
    private Object getBean(MyBeanDefiniton beanDefiniton) {
        Class<?> beanClazz = beanDefiniton.getBeanClazz();
        Object bean = beanMapByClass.get(beanClazz);
        if (bean != null) {
            return bean;
        }
		// 0
        bean = generateBeanInstance(beanClazz);


        // 1 先行暴露,解決循環依賴問題
        beanMapByClass.put(beanClazz, bean);
        beanMapByName.put(beanDefiniton.getBeanName(), bean);

        // 2 查找依賴
        List<Field> dependencysByField = beanDefiniton.getDependencysByField();
        if (dependencysByField == null) {
            return bean;
        }
		
        // 3
        for (Field field : dependencysByField) {
            try {
                autowireField(beanClazz, bean, field);
            } catch (Exception e) {
                throw new RuntimeException(beanClazz.getName() + " 創建失敗",e);
            }
        }

        return bean;
    }
}

大家看上面的代碼,我只定義了一個field,就是一個map,存放bean的class-》bean。

    /**
     * map:存儲 bean的class-》bean實例
     */
    private Map<Class, Object> beanMapByClass = new ConcurrentHashMap<>();
  • 0處,生成bean,直接就是new
  • 1處,先把這個不完整的bean,放進map
  • 2處,獲取需要注入的屬性集合
  • 3處,進行自動注入,就是根據field的Class,去map里查找對應的bean,設置到field里。

上面這個代碼,有啥問題沒?spring為啥整整三級?

ioc,一級緩存有什么問題

一級緩存的問題在於,就1個map,里面既有完整的已經ready的bean,也有不完整的,尚未設置field的bean。

如果這時候,有其他線程去這個map里獲取bean來用怎么辦?拿到的bean,不完整,怎么辦呢?屬性都是null,直接空指針了。

所以,我們就要加一個map,這個map,用來存放那種不完整的bean。這里,還是拿spring舉例。我們可以只用下面這兩層:

	/** 1級緩存 Cache of singleton objects: bean name to bean instance. */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** 2級緩存 Cache of early singleton objects: bean name to bean instance. */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

因為spring代碼里是三級緩存,所以我們對源碼做一點修改。

修改spring源碼,只使用二級緩存

修改創建bean的代碼,不放入第三級緩存,只放入第二級緩存

創建了bean之后,屬性注入之前,將創建出來的不完整bean,放到earlySingletonObjects

這個代碼,在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean,我這邊只有4.0版本的spring源碼工程,不過這套邏輯,算是spring核心邏輯,和5.x版本差別不大。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
            // 1
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
		Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
		...
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
            // 2
			earlySingletonObjects.put(beanName,bean);
			registeredSingletonObjects.add(beanName);
			// 3
//			addSingletonFactory(beanName, new ObjectFactory() {
//				public Object getObject() throws BeansException {
//					return getEarlyBeanReference(beanName, mbd, bean);
//				}
//			});
		}
  • 1處,就是創建對象,就是new
  • 2處,這是我加的代碼,放入二級緩存
  • 3處,本來這就是增加三級緩存的位置,被我注釋了。現在,就不會往三級緩存放東西了

修改獲取bean的代碼,只從第一、第二級緩存獲取,不從第三級獲取

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

之前的代碼是文章開頭那樣的,我這里修改為:

	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				return singletonObject;
			}
		}
		return (singletonObject != NULL_OBJECT ? singletonObject : null);

這樣,就是只用兩級緩存了。

兩級緩存,有啥問題?

ioc循環依賴,一點問題都沒有,完全夠用了。

我這邊一個簡單的例子,


public class Chick{
    private Egg egg;

    public Egg getEgg() {
        return egg;
    }

    public void setEgg(Egg egg) {
        this.egg = egg;
    }
}

public class Egg {
    private Chick chick;

    public Chick getChick() {
        return chick;
    }

    public void setChick(Chick chick) {
        this.chick = chick;
    }
    <bean id="chick" class="foo.Chick" lazy-init="true">
        <property name="egg" ref="egg"/>
    </bean>
    <bean id="egg" class="foo.Egg" lazy-init="true">
        <property name="chick" ref="chick"/>
    </bean>
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                "context-namespace-test-aop.xml");

        Egg egg = (Egg) ctx.getBean(Egg.class);

結論:

所以,一級緩存都能解決的問題,二級當然更沒問題。

但是,如果我這里給上面的Egg類,加個切面(aop的邏輯,意思就是最終會生成Egg的一個動態代理對象),那還有問題沒?

    <aop:config>
        <aop:pointcut id="mypointcut" expression="execution(public * foo.Egg.*(..))"/>
        <aop:aspect id="myAspect" ref="performenceAspect">
            <aop:after method="afterIncubate" pointcut-ref="mypointcut"/>
        </aop:aspect>
    </aop:config>

注意這里的切點:

execution(public * foo.Egg.*(..))

就是切Egg類的方法。

加了這個邏輯后,我們繼續運行,在 Egg egg = (Egg) ctx.getBean(Egg.class);行,會拋出如下異常:

我塗掉了一部分,因為那是官方對這個異常的推論,因為我們改了代碼,所以推論不准確,因此干脆隱去。

這個異常是說:

兄弟啊,bean egg已經被注入到了其他bean:chick中。(因為我們循環依賴了),但是,注入到chick中的,是Egg類型。但是,我們這里最后對egg這個bean,進行了后置處理,生成了代理對象。那其他bean里,用原始的bean,是不是不太對啊?

所以,spring給我們拋錯了。

怎么理解呢? 以io流舉例,我們一開始都是用的原始字節流,然后給別人用的也是字節流,但是,最后,我感覺不方便,我自己悄悄弄了個緩存字符流(類比代理對象),我是方便了,但是,別人用的,還是原始的字節流啊。

你bean不是單例嗎?不能這么玩吧?

所以,這就是二級緩存,不能解決的問題。

什么問題?aop情形下,注入到其他bean的,不是最終的代理對象。

三級緩存,怎么解決這個問題

要解決這個問題,必須在其他bean(chick),來查找我們(以上面例子為例,我們是egg)的時候,查找到最終形態的egg,即代理后的egg。

怎么做到這點呢?

加個三級緩存,里面不存具體的bean,里面存一個工廠對象。通過工廠對象,是可以拿到最終形態的代理后的egg。

ok,我們將前面修改的代碼還原:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
            // 1
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
		Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
            // 2
//			Map<String, Object> earlySingletonObjects = this.getEarlySingletonObjects();
//			earlySingletonObjects.put(beanName,bean);
//
//			Set<String> registeredSingletonObjects = this.getRegisteredSingletonObjects();
//			registeredSingletonObjects.add(beanName);
			
            // 3
			addSingletonFactory(beanName, new ObjectFactory() {
				public Object getObject() throws BeansException {
					return getEarlyBeanReference(beanName, mbd, bean);
				}
			});
		}
  • 1處,創建bean,單純new,不注入

  • 2處,revert我們的代碼

  • 3處,這里new了一個ObjectFactory,然后會存入到如下的第三級緩存。

    	/** 3級緩存 Cache of singleton factories: bean name to ObjectFactory. */
    	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    

    注意,new一個匿名內部類(假設這個匿名類叫AA)的對象,其中用到的外部類的變量,都會在AA中隱式生成對應的field。

    大家看上圖,里面的3個字段,和下面代碼1處中的,幾個字段,是一一對應的。

    			addSingletonFactory(beanName, new ObjectFactory() {
    				public Object getObject() throws BeansException {
                        // 1
    					return getEarlyBeanReference(beanName, mbd, bean);
    				}
    			});
    

ok,現在,egg已經把自己存進去了,存在了第三級緩存,1級和2級都沒有,那后續chick在使用getSingleton查找egg的時候,就會進入下面的邏輯了(就是文章開頭的那段代碼,下面已經把我們的修改還原了):

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//		Object singletonObject = this.singletonObjects.get(beanName);
//		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//			synchronized (this.singletonObjects) {
//				singletonObject = this.earlySingletonObjects.get(beanName);
//				return singletonObject;
//			}
//		}
//		return (singletonObject != NULL_OBJECT ? singletonObject : null);

		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
                        // 1
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return (singletonObject != NULL_OBJECT ? singletonObject : null);
	}

上面就會進入1處,調用singletonFactory.getObject();

而前面我們知道,這個factory的邏輯是:

			addSingletonFactory(beanName, new ObjectFactory() {
				public Object getObject() throws BeansException {
                    // 1
					return getEarlyBeanReference(beanName, mbd, bean);
				}
			});

1處就是這個工廠方法的邏輯,這里面,簡單說,就會去調用各個beanPostProcessor的getEarlyBeanReference方法。

其中,主要就是aop的主力beanPostProcessor,AbstractAutoProxyCreator#getEarlyBeanReference

其實現如下:

	public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		this.earlyProxyReferences.add(cacheKey);
        // 1
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

這里的1處,就會去對egg這個bean,創建代理,此時,返回的對象,就是個代理對象了,那,注入到chick的,自然也是代理后的egg了。

關於SmartInstantiationAwareBeanPostProcessor

我們上面說的那個getEarlyBeanReference就在這個接口中。

這個接口繼承了BeanPostProcessor

而創建代理對象,目前就是在如下兩個方法中去創建:

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    
	Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;    
}

這兩個方法,都是在實例化之后,創建代理。那我們前面創建代理,是在依賴解析過程中:

public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {
    ...
	Object getEarlyBeanReference(Object bean, String beanName) throws BeansException;
}

所以,spring希望我們,在這幾處,要返回同樣的對象,即:既然你這幾處都要返回代理對象,那就不能返回不一樣的代理對象。

那我們再看看,到底,AbstractAutoProxyCreator有沒有遵守約定呢,這幾個方法里,有沒有去返回同樣的代理包裝對象呢?

getEarlyBeanReference

	public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 1
		this.earlyProxyReferences.add(cacheKey);
		return wrapIfNecessary(bean, beanName, cacheKey);
	}

1處,往field:

private final Set<Object> earlyProxyReferences =
      Collections.newSetFromMap(new ConcurrentHashMap<Object, Boolean>(16));

里,加了個cachekey,這個cachekey,主要也就是如下的字符串,用來唯一標識而已。

	protected Object getCacheKey(Class<?> beanClass, String beanName) {
		return beanClass.getName() + "_" + beanName;
	}

我們可以看看這個field在哪里被用到了。

也就兩處,一處就是當前位置;另外一處,下面講。

這里,主要就是看看到底要不要生成代理對象,要的話,就生成,不要就算了,另外,做了個標記:在earlyProxyReferences加了當前bean的key,表示:當前bean,已經被getEarlyBeanReference方法處理過了。

至於,最終到底有沒有生成代理對象,另說。畢竟調用wrapIfNecessary也不是說,一定就滿足切面,要生成代理對象。

可能返回的仍然是原始對象。

postProcessBeforeInitialization

public Object postProcessBeforeInitialization(Object bean, String beanName) {
   return bean;
}

這一處,沒做處理。

postProcessAfterInitialization

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
            // 1
			if (!this.earlyProxyReferences.contains(cacheKey)) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

這里,1處這個判斷哈,就用到了前面我們說的那個field。那個field,只在兩處用,一處就是調用getEarlyBeanReference,會往里面把當前bean的key放進去;另外一處,就是這里。

這里判斷,如果field里不包含當前bean,就去調用wrapIfNecessary;如果包含(意味着,getEarlyBeanReference處理過了),就不調用了。

這里,說到底,就是保證了,wrapIfNecessary只被調用一次。

看吧,wrapIfNecessary也就這兩處被調用了。

所以,我們可以得出結論,在aop這個beanPostProcessor中,有多處機會可以返回一個proxy對象,但是,最終,只要在其中一處處理了,其他處,根本不再繼續處理。

另外,還有一點很重要,在這個aop beanPostProcessor中,傳入了原始的bean,我們會去判斷,是否要給它創建代理,如果要,就創建;如果不要則:

返回原始對象

整個流程串起來

上面這個后置處理器看明白了,接下來,再看看創建bean的核心流程:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
		// 1 
		BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
		final Object bean = instanceWrapper.getWrappedInstance();
		
		if (earlySingletonExposure) {
            // 2
			addSingletonFactory(beanName, new ObjectFactory() {
				@Override
				public Object getObject() throws BeansException {
					return getEarlyBeanReference(beanName, mbd, bean);
				}
			});
		}

		// 3 
		Object exposedObject = bean;
    	// 4
        populateBean(beanName, mbd, instanceWrapper);
		
    	// 5
        if (exposedObject != null) {
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }

		if (earlySingletonExposure) {
            // 6
			Object earlySingletonReference = getSingleton(beanName, false);
            
			if (earlySingletonReference != null) {
                // 7
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                    // 8
                    ...
				}
			}
		}

		return exposedObject;
	}

上面流程中,做了部分刪減。但基本創建一個bean,就這幾步了。

  • 1處,創建bean對象,此時,屬性什么的全是null,可以理解為,只是new了,field還沒設置

  • 2處,添加到第三級緩存;加進去的,只是個factory,只有循環依賴的時候,才會發揮作用

  • 3處,把原始bean,存到exposedObject

  • 4處,填充屬性;循環依賴情況下,A/B循環依賴。假設當前為A,那么此時填充A的屬性的時候,會去:

    new B;

    填充B的field,發現field里有一個是A類型,然后就去getBean("A"),然后走到第三級緩存,拿到了A的ObjectFactory,然后調用ObjectFactory,然后調用AOP的后置處理器類:getEarlyBeanReference,拿到代理后的bean(假設此處切面滿足,要創建代理);

    經過上面的步驟后,B里面,field已經填充ok,其中,且填充的field是代理后的A,這里命名為proxy A。

    B 繼續其他的后續處理。

    B處理完成后,被填充到當前的origin A(原始A)的field中

  • 5處,對A進行后置處理,此時調用aop后置處理器的,postProcessAfterInitialization;前面我們說了,此時不會再去調用wrapIfNecessary,所以這里直接返回原始A,即 origin A

  • 6處,去緩存里獲取A,拿到的A,是proxy A

  • 7處,我們梳理下:

    exposedObject:origin A

    bean:原始A

    earlySingletonReference: proxy A

    此時,下面這個條件是滿足的,所以,exposedObject,最終被替換為proxy A:

    if (exposedObject == bean) {
        exposedObject = earlySingletonReference;
    }
    

源碼

文章用到的aop循環依賴的demo,自己寫一個也可以,很簡單:

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-aop-xml-demo-cycle-reference

更新於2020-07-01:回答評論區問題

問題描述:只使用第一、第二級緩存,是否可行

這兩個問題,本質是一個問題,就是,不用第三級的ObjectFactory行不行,代碼直接調用getEarlyBeanReference,拿到bean A的早期引用后,放到第二級緩存,后續bean B去解析依賴時,,注入的不就是最終形態的bean A了嗎。

我回答的時候,只是思考了一下,並沒有實際測試。下面我們修改下源碼,測試一下。

修改源碼

獲取單例的地方,修改為只使用第一、第二級緩存

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				return singletonObject;
			}
		}
		return (singletonObject != NULL_OBJECT ? singletonObject : null);
	}

創建bean的地方,修改代碼,獲取earlyBeanReference后,手動放入第二級緩存

如下是原始代碼:

AbstractAutowireCapableBeanFactory#doCreateBean
	// 原始代碼長這樣
    addSingletonFactory(beanName, new ObjectFactory() {

        @Override
        public Object getObject() throws BeansException {
            Object earlyBeanReference = getEarlyBeanReference(beanName, mbd, bean);

            if (earlyBeanReference != bean) {
                log.info("{} has been wrapped to {}", bean,earlyBeanReference);
            }

            return earlyBeanReference;
        }
    });    

修改后,長這樣:

/**
 * 只使用第一、第二級緩存,即,只使用:
 * singletonObjects
 * earlySingletonObjects
 * 不使用第三級:
 * singletonFactories
 */
// 1
Object earlyBeanReference = getEarlyBeanReference(beanName, mbd, bean);
// 2
addEarlyReference(beanName, earlyBeanReference);

1處,主要是,獲取了早期引用,然后2處,調用我們自己寫的一個方法:

DefaultSingletonBeanRegistry#addEarlyReference
/**
 * 修改版本,直接添加早期引用
 * {@link SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(Object, String)}
 * 拿到早期引用,然后添加到earlySingletonObjects
 * 不使用第三級緩存singletonFactories
 * @param beanName
 * @param earlyReference  調用getEarlyBeanReference(java.lang.Object, java.lang.String)
 */
protected void addEarlyReference(String beanName, Object earlyReference) {
   synchronized (this.singletonObjects) {
      if (!this.singletonObjects.containsKey(beanName)) {
         // 1
         this.earlySingletonObjects.put(beanName,earlyReference);
         this.registeredSingletons.add(beanName);
      }
   }
}

1處代碼,把早期引用,存到了二級緩存。

測試該場景下有什么問題

  1. 首先,我們繼續用前面的例子,我們要getBean(egg),egg這個bean,依賴了chick,chick又依賴egg。egg最終要生成代理對象。

  2. 首先,創建egg,獲取其早期引用,放到二級緩存。

    這里注意,egg雖然生成了代理對象,但是,其屬性,chick是null。

  3. 接下來,開始egg解析依賴的時候,發現依賴了chick,所以,會去創建chick,並創建chick的早期引用

    到此為止,早期引用中,已經存放了兩個對象,egg(代理對象,但chick屬性為null),chick。

  4. 填充chick的field:egg

此時,chick已經好了。唯一的問題是,egg還沒好,egg里面的chick屬性,還是null。

  1. egg此時的field依賴,chick已經解決了,此時的egg,長這樣:

  2. 此時,egg的屬性已經填充了,是不是該去生成代理了

	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
            // 1
			if (!this.earlyProxyReferences.contains(cacheKey)) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

不過這里的1處,earlyProxyReferences已經包含了egg這個bean了,所以不會再去生成代理。

  1. 使用不完整的早期引用,替換了populateDependency完成的egg,出大事了

    if (earlySingletonExposure) {
    			Object earlySingletonReference = getSingleton(beanName, false);
    			if (earlySingletonReference != null) {
                    // 1
    				if (exposedObject == bean) {
                        // 2
    					exposedObject = earlySingletonReference;
    				}
                }
    

    此時,1處是滿足的,見上圖。所以,進入2處,2處的earlySingletonReference,就是我們從二級緩存拿到的早期引用,這個早期引用,一切都好,唯一的問題是,這里的field:chick是null

    所以,問題,就是這么個問題,只用二級緩存,此時的chick是null。

更新於2020-07-04:手動調用getEarlyReferencde,放到二級緩存,真的有問題嗎

這次的評論區問題,說實話,我非常感謝,因為,借此問題,我發現,我上面講的:

更新於2020-07-01的那部分,結論是錯誤的。

這個問題是什么意思呢?就是這位同學認為:

更新於2020-07-01那部分的試驗,最終我的結論是,最終這個Egg對象,是一個代理對象,但是其中的Chick field是null,所以,有問題。

但是,正常情況下,生成的代理對象,其中的field,本來就是null。

我舉個例子,是我們實際中的業務代碼:

@Service
public class SeatInformationServiceImpl extends ServiceImpl<SeatInformationMapper, SeatInformation> implements ISeatInformationService {
    @Autowired
    private SeatInformationMapper seatInformationMapper;
    @Autowired
    private ICenterService centerService;

    @Autowired
    private RestTemplate restTemplate;
    
    // 1
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(String token,Long seatId) throws BusinessException {
        ...
    }

這個service,就是我從業務代碼里copy的,其中,1處,注解了Transactional注解,而事務一般就是基於aop實現的,所以,最終這個service,肯定是會生成代理對象的。

我們看看這個生成的代理對象,長什么樣子?

而代理對象,要獲取真正的target時,是可以拿到的,如下所示。

ok,所以,我2020-07-01的試驗中,過程是沒問題的,但結論有問題。

  • 錯誤結論:手動調用getEarlyReference放入二級緩存,去掉三級緩存,這樣問題
  • 正確結論:手動調用getEarlyReference放入二級緩存,去掉三級緩存,這樣沒有問題

而且,關鍵是,我在這種試驗場景下,最終去執行如下代碼,切面也是生效了的:

public static void main(String[] args) {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
            "context-namespace-test-aop.xml");

    Egg egg = (Egg) ctx.getBean(Egg.class);
    egg.incubate();
}

開始孵化
09:45:41.754 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'performenceAspect'
孵化完成 --------這部分是切面打印的

既然先行手動調用getEarlyBeanReference這種方案可以解決問題,為什么還要弄三級緩存

問題就在於,我們每次都去調用getEarlyReference,是可以解決問題,沒錯。但是,這一步,很多時候都是沒有必要的。

在沒有循環依賴的時候,這個方法,是從來不會被調用的;也就是說,我們為了解決系統中那百分之1可能出現的循環依賴問題,而讓百分之99的bean,創建時,都去走上這么一圈。

效率說不過去吧?

ok,大家要明白,這個方法,SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference,其存在的意義,就是為了解決aop場景下的循環依賴。

沒有這個場景,就不需要這個方案。

大家可以仔細思考下,上面的紅圈這里,這個條件,什么時候才會是true?

不錯的參考資料

https://blog.csdn.net/f641385712/article/details/92801300

總結

如果有問題,歡迎指出;歡迎加群討論;有幫助的話,請點個贊吧,謝謝


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM