寫在前面的話
相關背景及資源:
曹工說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,自己寫一個也可以,很簡單:
更新於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處代碼,把早期引用,存到了二級緩存。
測試該場景下有什么問題
-
首先,我們繼續用前面的例子,我們要getBean(egg),egg這個bean,依賴了chick,chick又依賴egg。egg最終要生成代理對象。
-
首先,創建egg,獲取其早期引用,放到二級緩存。
這里注意,egg雖然生成了代理對象,但是,其屬性,chick是null。
-
接下來,開始egg解析依賴的時候,發現依賴了chick,所以,會去創建chick,並創建chick的早期引用
到此為止,早期引用中,已經存放了兩個對象,egg(代理對象,但chick屬性為null),chick。
-
填充chick的field:egg
此時,chick已經好了。唯一的問題是,egg還沒好,egg里面的chick屬性,還是null。
-
egg此時的field依賴,chick已經解決了,此時的egg,長這樣:
-
此時,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了,所以不會再去生成代理。
-
使用不完整的早期引用,替換了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
總結
如果有問題,歡迎指出;歡迎加群討論;有幫助的話,請點個贊吧,謝謝