今天這一篇主要想圍繞著Spring的循環(huán)依賴問題以及終極靈魂拷問如何手寫Spring的問題講講。
一刊愚、Spring循環(huán)依賴
1.什么是循環(huán)依賴
Spring中的循環(huán)依賴一直是Spring中一個(gè)很重要的話題,一方面是因?yàn)樵创a中為了解決循環(huán)依賴做了很多處理踩验,另外一方面是因?yàn)槊嬖嚨臅r(shí)候鸥诽,如果問到Spring中比較高階的問題,那么循環(huán)依賴必定逃不掉箕憾。所以還是可以看一下這塊的源碼牡借,看看Spring是如何解決循環(huán)依賴的問題的。
Spring中之所以會(huì)出現(xiàn)循環(huán)依賴跟Bean的生命周期有關(guān)系袭异,在創(chuàng)建一個(gè)Bean的過程中如果依賴的另外一個(gè)Bean還沒有創(chuàng)建钠龙,就會(huì)需要去創(chuàng)建依賴的那個(gè)Bean,而如果兩個(gè)Bean相互依賴的話,就會(huì)出現(xiàn)循環(huán)依賴的問題碴里。體現(xiàn)到代碼層次就是像下面這個(gè)樣子的沈矿,比如兩個(gè)對(duì)象相互依賴:
@Component
public class A {
// A中注入了B
@Autowired
private B b;
}
@Component
public class B {
// B中也注入了A
@Autowired
private A a;
}
或者自己依賴自己
@Component
public class A {
// A中注入了A
@Autowired
private A a;
}
2.三級(jí)緩存方案
假設(shè)按照上面代碼Class A 和 B,按照從A->B的順序來實(shí)例化咬腋,Spring創(chuàng)建bean的過程可以分為三個(gè)階段:
1羹膳、實(shí)例化,對(duì)應(yīng)方法:AbstractAutowireCapableBeanFactory # createBeanInstance
方法
2根竿、屬性注入陵像,對(duì)應(yīng)方法:AbstractAutowireCapableBeanFactory # populateBean
方法
3、初始化寇壳,對(duì)應(yīng)方法:AbstractAutowireCapableBeanFactory # initializeBean
所以執(zhí)行順序是先在這個(gè)類中的 AbstractBeanFactory 按調(diào)用鏈執(zhí)行如下三個(gè)方法:
//AbstractBeanFactory
1醒颖、getBean("a")
2、doGetBean("a")
3壳炎、getSingleton("a")
在調(diào)用getSingleton(a)
方法泞歉,這個(gè)方法又會(huì)調(diào)用getSingleton(beanName, true)
,所以才進(jìn)入到下面這個(gè)方法:
//DefaultSingletonBeanRegistry
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
getSingleton(beanName, true)
這是個(gè)重點(diǎn)方法冕广,該方法實(shí)際上就是到緩存中嘗試去獲取Bean疏日,整個(gè)緩存分為三級(jí)
singletonObjects
,一級(jí)緩存撒汉,存儲(chǔ)的是所有創(chuàng)建好了的單例Bean
earlySingletonObjects
沟优,完成實(shí)例化,但是還未進(jìn)行屬性注入及初始化的對(duì)象
singletonFactories
睬辐,提前暴露的一個(gè)單例工廠挠阁,二級(jí)緩存中存儲(chǔ)的就是從這個(gè)工廠中獲取到的對(duì)象
//DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//一級(jí)緩存中獲取-->完整bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//二級(jí)緩存中-->獲取未屬性注入的bean
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//三級(jí)緩存中-->獲取工廠
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//三級(jí)緩存中的工廠getObject的對(duì)象-->放入二級(jí)緩存中
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
因?yàn)槭堑谝淮蝿?chuàng)建,因此上面的三級(jí)緩存都未命中溯饵,此時(shí)會(huì)進(jìn)入getSingleton的另外一個(gè)重載方法getSingleton(beanName, singletonFactory)侵俗。這里我們知道 singletonFactory 是需要等待createBean(beanName, mbd, args) 方法的返回,然后作為第二個(gè)輸入?yún)?shù)給到下面 getSingleton 方法丰刊。
createBean 方法的返回將作為 getSingleton 的輸入隘谣,然后會(huì)進(jìn)入到下面這段代碼中:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//省略...
try {
//入?yún)⒌膌ambda會(huì)提供一個(gè)singletonFactory
//調(diào)用createBean方法創(chuàng)建一個(gè)Bean后返回
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
//省略...
if (newSingleton) {
//添加到一級(jí)緩存singletonObjects中
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
上面的代碼主要實(shí)現(xiàn)了:將已經(jīng)完全創(chuàng)建好了的單例Bean放入一級(jí)緩存中。在前面一步 createBean()方法的創(chuàng)建實(shí)例過程中還有一個(gè) doCreateBean 方法啄巧,里面還有這樣一段代碼:
這個(gè)也就是在Bean實(shí)例化后寻歧,屬性注入之前Spring將Bean包裝成一個(gè)工廠添加進(jìn)了三級(jí)緩存中,addSingletonFactory 對(duì)應(yīng)源碼如下:
// 這里傳入的參數(shù)也是一個(gè)lambda表達(dá)式秩仆,() -> getEarlyBeanReference(beanName, mbd, bean)
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// 添加到三級(jí)緩存中
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
那么getEarlyBeanReference方法又做了什么呢码泛?進(jìn)入源碼看下:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
非AOP的二級(jí)緩存
這個(gè)地方的BeanPostProcessor后置處理器,只在處理AOP的實(shí)例對(duì)象時(shí)才會(huì)發(fā)揮作用澄耍,如果不考慮AOP噪珊,代碼就是:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
return exposedObject;
}
可見晌缘,對(duì)于非Aop實(shí)例對(duì)象,這個(gè)工廠直接將實(shí)例化階段創(chuàng)建的對(duì)象返回了痢站!
現(xiàn)在整體來梳理一下磷箕,繼續(xù)走A對(duì)象創(chuàng)建的流程,通過this.singletonFactories.put(beanName, singletonFactory)
這個(gè)方法只是添加了一個(gè)工廠瑟押,通過這個(gè)工廠(ObjectFactory)的getObject方法可以得到一個(gè)對(duì)象搀捷。當(dāng)A完成了實(shí)例化并添加進(jìn)了三級(jí)緩存后,就要開始為A進(jìn)行屬性注入了多望,在注入時(shí)發(fā)現(xiàn)A依賴了B嫩舟,那么這個(gè)時(shí)候Spring又會(huì)去getBean(b),然后反射調(diào)用setter方法完成屬性注入怀偷。因?yàn)锽需要注入A家厌,所以在創(chuàng)建B的時(shí)候,又會(huì)去調(diào)用getBean(a)椎工,這個(gè)時(shí)候就又回到之前的流程了饭于,但是不同的是,之前的getBean是為了創(chuàng)建Bean维蒙,而此時(shí)再調(diào)用getBean不是為了創(chuàng)建了掰吕,而是要從緩存中獲取,因?yàn)橹癆在實(shí)例化后已經(jīng)將其放入了三級(jí)緩存singletonFactories中颅痊,此時(shí)getBean(a)的二級(jí)緩存會(huì)通過調(diào)用三級(jí)緩存的facotry殖熟,通過工廠的getObject方法將對(duì)象放入到二級(jí)緩存中并返回,所以此時(shí)getBean(a)的流程就是這樣子了斑响,一個(gè)清晰的流程圖如下:
結(jié)合了AOP的循環(huán)依賴
如果在開啟AOP的情況下菱属,那么就是調(diào)用 getEarlyBeanReference 方法對(duì)應(yīng)的源碼如下:
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
// 如果需要代理,返回一個(gè)代理對(duì)象舰罚,不需要代理纽门,直接返回當(dāng)前傳入的這個(gè)bean對(duì)象
return wrapIfNecessary(bean, beanName, cacheKey);
}
對(duì)A進(jìn)行了AOP代理的話,那么此時(shí)getEarlyBeanReference將返回一個(gè)代理后的對(duì)象营罢,而不是實(shí)例化階段創(chuàng)建的對(duì)象赏陵,這樣就意味著B中注入的A將是一個(gè)代理對(duì)象而不是A的實(shí)例化階段創(chuàng)建后的對(duì)象。整個(gè)注入的流程圖就變成了如下:
圖片和思路整理自《面試必殺技饲漾,講一講Spring中的循環(huán)依賴》瘟滨,想要徹底搞懂可以刷這個(gè)文章理解。
3.循環(huán)依賴的總結(jié)
1能颁、Spring到底是如何解決循環(huán)依賴的呢,這里來一波文字的總結(jié):
Spring通過三級(jí)緩存解決了循環(huán)依賴倒淫,其中一級(jí)緩存為單例池(singletonObjects),二級(jí)緩存為早期曝光對(duì)象earlySingletonObjects伙菊,三級(jí)緩存為早期曝光對(duì)象工廠(singletonFactories)。當(dāng)A、B兩個(gè)類發(fā)生循環(huán)引用時(shí)镜硕,在A完成實(shí)例化后运翼,就使用實(shí)例化后的對(duì)象去創(chuàng)建一個(gè)對(duì)象工廠,并添加到三級(jí)緩存中兴枯,如果A被AOP代理血淌,那么通過這個(gè)工廠獲取到的就是A代理后的對(duì)象,如果A沒有被AOP代理财剖,那么這個(gè)工廠獲取到的就是A實(shí)例化的對(duì)象悠夯。當(dāng)A進(jìn)行屬性注入時(shí),會(huì)去創(chuàng)建B躺坟,同時(shí)B又依賴了A沦补,所以創(chuàng)建B的同時(shí)又會(huì)去調(diào)用getBean(a)來獲取需要的依賴,此時(shí)的getBean(a)會(huì)從緩存中獲取咪橙,第一步夕膀,先獲取到三級(jí)緩存中的工廠;第二步美侦,調(diào)用對(duì)象工工廠的getObject方法來獲取到對(duì)應(yīng)的對(duì)象产舞,得到這個(gè)對(duì)象后將其注入到B中。緊接著B會(huì)走完它的生命周期流程菠剩,包括初始化易猫、后置處理器等。當(dāng)B創(chuàng)建完后赠叼,會(huì)將B再注入到A中擦囊,此時(shí)A再完成它的整個(gè)生命周期。至此嘴办,循環(huán)依賴結(jié)束瞬场!
2、為啥要用三級(jí)緩存涧郊,是否可以用二級(jí)緩存
在普通的循環(huán)依賴的情況下贯被,三級(jí)緩存沒有任何作用。三級(jí)緩存實(shí)際上跟Spring中的AOP相關(guān)妆艘。AOP場(chǎng)景下的getEarlyBeanReference 會(huì)拿到一個(gè)代理的對(duì)象彤灶,但是不確定有沒有依賴,需不需要用到這個(gè)依賴對(duì)象批旺,所以先給一個(gè)工廠放到三級(jí)緩存里幌陕。
這個(gè)工廠的目的在于延遲對(duì)實(shí)例化階段生成的對(duì)象的代理,只有真正發(fā)生循環(huán)依賴的時(shí)候汽煮,才去提前生成代理對(duì)象搏熄,否則只會(huì)創(chuàng)建一個(gè)工廠并將其放入到三級(jí)緩存中棚唆,但是不會(huì)去通過這個(gè)工廠去真正創(chuàng)建對(duì)象。
二心例、如何手寫一個(gè)Spring框架
1宵凌、一個(gè)手寫IoC容器的思路
IOC的實(shí)現(xiàn)思路如下:
- 首先有一個(gè)配置文件定義了應(yīng)用的基礎(chǔ)包, 也就是Java源碼路徑.
- 讀取基礎(chǔ)包名, 然后通過類加載器獲取到應(yīng)用中所有的Class對(duì)象, 存儲(chǔ)到一個(gè)集合中.
- 獲取應(yīng)用中所有Bean (Controller和Service) 的Class對(duì)象, 通過反射創(chuàng)建實(shí)例, 然后存儲(chǔ)到 Bean容器中.
- 遍歷Bean容器中的所有Bean, 為所有帶 @Autowired 注解的屬性注入實(shí)例.
- IOC操作要在應(yīng)用啟動(dòng)時(shí)就完成, 所以必須寫在靜態(tài)代碼塊中.
2、一個(gè)手寫SpringMVC的思路
(1)讀取配置
SpringMVC本質(zhì)上是一個(gè)Servlet,這個(gè) Servlet 繼承自 HttpServlet止后。FrameworkServlet負(fù)責(zé)初始化SpringMVC的容器瞎惫,并將Spring容器設(shè)置為父容器。因?yàn)楸疚闹皇菍?shí)現(xiàn)SpringMVC译株,對(duì)于Spring容器不做過多講解瓜喇。
為了讀取web.xml中的配置,我們用到ServletConfig這個(gè)類古戴,它代表當(dāng)前Servlet在web.xml中的配置信息欠橘。通過web.xml中加載我們自己寫的MyDispatcherServlet和讀取配置文件。
(2)初始化階段
在前面我們提到DispatcherServlet的initStrategies方法會(huì)初始化9大組件现恼,但是這里將實(shí)現(xiàn)一些SpringMVC的最基本的組件而不是全部肃续,按順序包括:
- 加載配置文件
- 掃描用戶配置包下面所有的類
- 拿到掃描到的類,通過反射機(jī)制叉袍,實(shí)例化始锚。并且放到ioc容器中(Map的鍵值對(duì) beanName-bean) beanName默認(rèn)是首字母小寫
- 初始化HandlerMapping,這里其實(shí)就是把url和method對(duì)應(yīng)起來放在一個(gè)k-v的Map中,在運(yùn)行階段取出
(3)運(yùn)行階段
每一次請(qǐng)求將會(huì)調(diào)用doGet或doPost方法喳逛,所以統(tǒng)一運(yùn)行階段都放在doDispatch方法里處理瞧捌,它會(huì)根據(jù)url請(qǐng)求去HandlerMapping中匹配到對(duì)應(yīng)的Method,然后利用反射機(jī)制調(diào)用Controller中的url對(duì)應(yīng)的方法润文,并得到結(jié)果返回姐呐。按順序包括以下功能:
- 異常的攔截
- 獲取請(qǐng)求傳入的參數(shù)并處理參數(shù)
- 通過初始化好的handlerMapping中拿出url對(duì)應(yīng)的方法名,反射調(diào)用
3典蝌、一個(gè)手寫SpringMVC的思路
1 掃描 aop 包曙砂, 獲取 aspect 的類
2 根據(jù) 切點(diǎn) 獲取該切點(diǎn)的 類 和 方法
3 根據(jù)配置的 類 和 方法 為該類生成一個(gè)代理對(duì)象
4 將改代理對(duì)象放入 bean Map 中
5 調(diào)用的時(shí)候 將代理對(duì)象 轉(zhuǎn)換成需要的對(duì)象