Spring原始碼之建立單例物件
技術標籤:spring原始碼spring設計模式springiocjava設計模式程式語言
相信只要從事過java程式開發的程式設計師就沒有沒聽說過Spring框架的。對於Java後端開發者來說,Spring簡直是神兵利器一樣的存在。而Spring原始碼又是非常優質的程式碼,裡面充分利用了各種設計模式,對於程式設計師來說,能夠看懂、理解Spring原始碼的設計,無論是面試還是應用到開發中,都是對自己有非常大的裨益的。
Spring有兩個非常重要的功能,一個是IOC,一個是AOP。今天我們所要探討的物件的建立就屬於IOC層面。IOC(控制反轉),是由Spring框架提供,來幫助開發者建立bean物件、管理bean物件的。我們今天就通過debug原始碼的方式瞭解一下單例物件的建立。
一、 準備工作
首先讓我們來準備一個我們將要生成的物件。
public class A {
private String aname;
public String getAname() {
return aname;
}
public void setAname(String aname) {
this.aname = aname;
}
}
配置檔案中定義bean資訊
<bean id="a" class="com.mao.springstudy.A">
<property name="aname" value="我是A"></property>
</bean>
在main方法中進行程式碼編寫
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
A a = (A) context.getBean("a");
System.out.println("a的name" + a.getAname());
二、跟著debug進入原始碼
1.ClassPathXmlApplicationContext
點選執行,跟著除錯斷點走到ClassPathXmlApplicationContext的構造方法裡。
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
其中setConfigLocation(configLocations)是用來設定本地的配置資訊。
重點是refresh()方法。
根據斷點除錯我們可以發現,這個
refresh()
方法實際是呼叫了AbstractApplicationContext中的refresh()
方法。這個refresh()
方法是AbstractApplicationContext的一個模板方法,由子類呼叫。
2.refresh
在refresh()
的十三個方法中,建立物件的方法是finishBeanFactoryInitialization(beanFactory)
,原始碼中給的註釋是“Instantiate all remaining (non-lazy-init) singletons.”,翻譯過來也就是“例項化所有剩餘的(非延遲初始化)單例。”。
跟著除錯點進方法中,找到beanFactory.preInstantiateSingletons();
,繼續點進這個方法。我們看到有一個ArrayList,這個ArrayList就是將配置檔案中的bean轉成beanDefinition的name的集合,也就表示了我們將要建立物件的數量。接下來通過for迴圈,將物件創建出來。
3.getBean(beanName)和doGetBean()
讓我們跟著斷點繼續走,斷點進到了getBean(beanName)
的方法中,從這裡開始,就將進入建立物件的步驟中。進入到“getBean()”方法中。
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
可以看到,getBean方法又呼叫了doGetBean()
方法。
這裡重點強調下,在spring原始碼中,真正“做事”的方法都是以“do”開頭的。比如說這裡的getBean和doGetBean方法,真正去獲取Bean物件的實際是doGetBean方法。讓我們跟著斷點繼續走到
doGetBean()
方法中。
在doGetBean()
方法中,先呼叫getSingleton(beanName)
方法,這個方法是個非常重要的方法,它是從spring的三級快取中查詢物件是否已被建立。三級快取的存在是spring為了解決物件迴圈依賴問題。我將在以後的文章中聊一聊spring迴圈依賴的問題。
跟著斷點繼續往下走,走到下方程式碼處。
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
根據if判斷條件我們可以知道,這裡是判斷我們所要建立的bean是否是單例模式,如果是單例模式的bean就進到if分支中。這裡的getSingleton()
方法有些特殊,它是以lambda表示式作為入參。我們進到getSingleton()
方法中粗略的看一下。在getSingleton()
方法中有一行程式碼
singletonObject = singletonFactory.getObject();
其中的signletonFactory就是我們傳入的lambda表示式。當呼叫了singletonFactory.getObject()
方法,就是呼叫了lambda表示式中的程式碼塊,也就是呼叫了lambda表示式中的createBean(beanName, mbd, args)
方法。
4.createBean()和doCreateBean()
從createBean()
的方法名字中我們就可以知道,這是一個用來建立Bean的方法。讓我們跟著斷點點進去。進去之後我們可以找到一個方法,叫做doCreateBean()
方法。根據我們上面強調的規則,這個方法是真正建立bean物件的方法,讓我們點進這個方法。在方法中我們可以看到這一段程式碼。
if (instanceWrapper == null) {
// createBeanInstance方法中通過反射呼叫bean的構造方法,例項化出bean
// 呼叫鏈 createBeanInstance -> instantiateBean -> SimpleInstantiationStrategy.instantiate
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
進到createBeanInstance(beanName, mbd, args)
方法中,跟著斷點走,一直走到方法最後一行instantiateBean(beanName, mbd)
方法中。方法中有以下程式碼。
// 呼叫策略類例項化bean
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
5.instantiateBean(beanName, mbd)(例項化出物件)
再進入instantiate()
方法中。跟著斷點繼續除錯,除錯到其中一行程式碼。
// 通過反射獲取bean構造方法
constructorToUse = clazz.getDeclaredConstructor();
!!!親人啊!看到這行程式碼就像看到了親人!!!這一行程式碼不就是java利用反射獲取構造方法嘛!
讓我們繼續往下看,獲取了構造方法後就是呼叫newInstance方法了。BeanUtils.instantiateClass(constructorToUse)
方法就是將建構函式傳過去呼叫newInstance方法,讓我們點進方法裡面去,在這個方法中我們可以找到:
return ctor.newInstance(argsWithDefaultValues);
到這裡,一個物件就算通過反射的方式例項化成功了。(筆芯)
6.物件屬性賦值及物件後置處理
讓我們繼續讓斷點走完。例項化物件後我們又回到了doCreateBean()
方法中。spring通過
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
這個方法,將例項化後的物件放入三級快取中第三級快取。接著通過
populateBean(beanName, mbd, instanceWrapper)
方法對物件的屬性進行賦值,在賦值完成操作後,再呼叫exposedObject = initializeBean(beanName, exposedObject, mbd)
方法,實現aware介面或者beanPostProcessors。
到這裡,基本一個單例物件的建立就結束了。
三、總結
讓我們來梳理一下spring中建立單例物件的脈絡。首先由AbstractApplicationContext的子類來呼叫refresh()
方法;refresh()
方法在初始化IOC容器、準備beanPostProcessor等操作後,呼叫finishBeanFactoryInitialization(beanFactory)
方法中的beanFactory.preInstantiateSingletons()
方法;在beanFactory.preInstantiateSingletons()
方法中找到getBean()
方法,找到getBean()
的doGetBean()
方法中的if(mbd.isSingleton())
判斷分支;在判斷分支中進入createBean()
的doCreateBean()
方法,這個方法是真正建立物件的方法;doCreateBean()
方法中找到createBeanInstance(beanName, mbd, args)
方法,該方法呼叫了instantiateBean(beanName, mbd)
方法;instantiateBean(beanName, mbd)
方法中通過呼叫了策略設計模式的instantiate(mbd, beanName, this)
方法,在instantiate()
方法中通過反射獲取到了物件的構造方法,再通過BeanUtils.instantiateClass(constructorToUse)
將物件例項化出來;例項化出物件後,再在doCreateBean()
方法中繼續呼叫populateBean(beanName, mbd, instanceWrapper)
方法和initializeBean(beanName, exposedObject, mbd)
,為物件賦值並實現aware和beanPostProcessor。到這裡一個物件基本就建立完成了。
閱讀原始碼是一個很枯燥的過程,不過這就像是在找寶藏一樣,在找到寶藏後,一身的疲憊和勞累會被喜悅衝散。
學 海無涯,學 無止境。
spring原始碼推薦學習地址:馬士兵講spring原始碼