1. 程式人生 > 其它 >Spring原始碼之建立單例物件

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原始碼