【學習底層原理系列】重讀spring原始碼1-建立基本的認知模型
開篇閒扯
在工作中,相信很多人都有這種體會,與其修改別人程式碼,寧願自己重寫。
為什麼?
先說為什麼願意自己寫:
從0-1的過程,是建立在自己已有認知基礎上,去用自己熟悉的方式構建一件作品。也就是說,
1.對目標的認知是熟悉的(當然每個人水平可能不一樣,也有可能是錯的,這不重要,重要的是自認為是符合的);
2.使用的工具是自己熟悉的。
接下來就是去做一件自己熟悉的領域的事而已。
再說為什麼不願意修改別人程式碼:
1.首先要在閱讀程式碼過程中,去不斷的檢視對方的實現是否符合自己的認知,很可能由於雙方認識程度不同,導致一開始對目標的認識就不一致。
2.實現方式是自己不熟悉的,要不斷的去適應對方的風格,要不斷的去猜測對方的用意,還要記憶大量的內容。
3.人都有趨利避害的心裡,對於自己不熟悉的東西進行閱讀,是有讀錯的風險的,如果再修改,那風險就更大了。對這種風險的規避,是來自骨子裡的。
在猿界,閱讀原始碼的經驗是非常被看重的
你很多人工作了很多年,卻總是不能沉下心來讀一讀
這裡扯了這麼多,算是一種讓自己心安的解釋吧
下面開始正文,嘗試換一種風格來解析spring原始碼。因為看了網上很多人寫的文章,就是粘了一堆程式碼,簡單的做了解釋,個人認為這並不具有可操作性,與其這樣看隻言片語的程式碼,還不如直接看原始碼來的完整。
Spring臨門一腳
我們知道,spring的兩大核心功能是IOC和AOP。其中IOC解決了我們用的例項的建立問題,AOP解決的是對方法的擴充套件問題。不管是出於什麼考慮,初衷都是為了減少程式碼編寫,減少在非業務開發之外的精力。
今天我們先來學習IOC:
依賴注入,或者叫控制反轉。不熟悉的可以自行查一下,無非概念而已。
Java是面向物件的語言,在沒有Spring之前,甚至於現在我們在開發過程中,需要用到某物件了,我們是這麼來做的:
MyObject obj=new MyObject(); ... obj.methodName(); ...
於是你會發現,到處都充斥著這種例項初始化的程式碼,可能在類變數裡,也可能在方法的區域性變數裡。於是有人就想了,我能不能把這些變數統一管理起來呢?比如統一放在類變數裡,可以在當前類例項化時在構造方法裡統一例項化,也可以在宣告時就例項化。比如這樣:
public class MySuperClass1{ MySubClassA subA=new MySubClassA(); MySubClassB subB=new MySubClassB();
MySuperClass2 super2=new MySuperClass2();
...
public void super1Methord(){
super2.super2Methord(this.subA);
} }
嗯,看起來好了很多,這樣在MySuperClass1類中,無論有多少個方法用到了那兩個sub類的例項,都不用再自己例項化了。
那麼問題來了,如果需要在方法super1Methord()中呼叫另一個類MySuperClass2的方法super2Methord()【如程式碼所示】,而這個方法也用到了MySubClassA的例項,怎麼辦?聰明的你肯定想到了,把物件作為引數傳遞進去,就如程式碼中一樣。
那麼問題又來了,假如在第三個類MySuperClass3中,存在著和MySuperClass1一樣的情況,那麼該怎麼辦呢?是不是還要自己建立物件,然後傳遞進去?這樣不就是重複建立嗎?那怎麼辦才能更好一些呢?
可能你會想到,我弄一個根類,所有類都繼承自這個類,在這個根類裡實例化好所有物件,然後就不用重複建立了。
是的,思路是對的,只是,這就需要自己來維護這些類,如果新增了,就要時刻記得去根類中新增一下,如果不需要了,要記得去根類中刪除下,專案小還好,專案大了,誰還記得哪個有用哪個沒用?最後這個根類,就誰都不敢輕易改。那有沒有什麼好的方式可以解決呢?比如我配置下,或者加個註解,這個根類就能自動識別我新加的類,就能給自動的例項化?
springIOC,就做了這件事。它提供了容器,也就是我們說的根類,我們在使用的時候,就可以通過名字或其他方式,從容器中拿到事先建立好的例項物件。
ApplicationContext ctx = new ClassPathXmlApplicationContext("aop-test.xml"); ITestBean testBean = (ITestBean) ((ClassPathXmlApplicationContext) ctx).getBean("testBean"); String str = testBean.getStr();
對應到原始碼裡,容器就是各種xxxApplicationContext,例如上面程式碼中的ClassPathXmlApplicationContext。
我覺得以上是一定要理解清晰的知識點。知道了what,再帶著疑問和目標去了解How,會事半功倍。
下面開始分析原始碼,在分析原始碼過程中,我會從繁雜的程式碼中把主流程梳理出來,嘗試去掉細枝末節,儘量保證思路的連貫性。
ApplicationContext ctx = new ClassPathXmlApplicationContext("aop-test.xml");
這就是初始化容器,我們跟進去,發現在其建構函式中,有一個核心方法是需要我們關注的:refresh()
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
為什麼這裡叫重新整理呢?不是應該叫建立、構造之類的嗎?
在介面註釋裡有一句話:
As this is a startup method, it should destroy already created singletons
作為一個啟動方法,它需要銷燬已建立的單例。
銷燬後然後再建立。這不就是重新整理的意思嗎。
繼續跟進去:
refresh()方法裡,最核心的12個方法,共同支撐起Spring的架子。
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1 重新整理前的預處理: prepareRefresh(); // 2 【建立bean】生成BeanFactory,並載入beanDefinition ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 3 對BeanFactory進行預處理: prepareBeanFactory(beanFactory); try { //4 空實現,暫時忽略 postProcessBeanFactory(beanFactory); // 5 【擴充套件點】新增BeanFactory的後置處理器BeanFactoryPostProcessor並執行之。用於在bean例項化之前,讀取beanDefinition並進行修改。 invokeBeanFactoryPostProcessors(beanFactory); // 6 【擴充套件點】:新增BeanPostProcessor,注意區別於上述的BeanFactoryPostProcessor,這裡只是新增,在第11步中,bean例項化時執行初始化方法之前後,可對bean進行修改 registerBeanPostProcessors(beanFactory); // 7 初始化MessageSource元件,做國際化功能:訊息繫結,訊息解析 initMessageSource(); // 8 【事件監聽】初始化事件處理器 initApplicationEventMulticaster(); // 9 在這裡是空實現 onRefresh(); // 10 【事件監聽】註冊監聽器,就是實現了ApplicationListener介面的bean,和上面的8搭配使用 registerListeners(); // 11 【建立bean】初始化非懶載入的單例bean,兩個作用
// .真正的例項化bean
// .實現Aop,建立代理bean finishBeanFactoryInitialization(beanFactory); // 12 【事件監聽】完成context的重新整理,釋出ContextRefereshedEvent事件 finishRefresh(); } catch (BeansException ex)// 忽略 } finally { // 忽略 } } }
其實歸納下,需要重點關注的就分為以下三類:
1.【建立bean】:2和11
2.【擴充套件點】:處理器註冊和執行,包括BeanFactoryPostProcessor和BeanPostProcessor:4,5,6,11
3.【事件監聽】:8,10,12
好,第一篇就先建立基本的印象。
&n