一文掌握 Spring 的啟動(dòng)擴(kuò)展點(diǎn)
在 Java 的世界中,我們知道 Spring 是當(dāng)下最主流的開發(fā)框架,沒有之一。而在使用 Dubbo、MyBatis 等開源框架時(shí),我們發(fā)現(xiàn)可以采用和 Spring 完全一樣的方式來使用它們。
圖 1 基于統(tǒng)一的方法使用不同的框架
可能你平時(shí)使用時(shí),并沒有意識(shí)到這一點(diǎn),但仔細(xì)想一想,你會(huì)覺得這是一件比較神奇的事情。本來就是不同的框架,怎么能夠無縫地集成在一起呢?這就是今天我們要討論的話題。Spring 為我們內(nèi)置了一組功能非常強(qiáng)大的啟動(dòng)擴(kuò)展點(diǎn)。通過這些啟動(dòng)擴(kuò)展點(diǎn),可以實(shí)現(xiàn)我們想要的集成效果。
系統(tǒng)初始化
我們先來看兩個(gè)非常常見的 Spring 啟動(dòng)擴(kuò)展點(diǎn) InitializingBean 和 DisposableBean。在 Spring 中,這兩個(gè)擴(kuò)展點(diǎn)分別作用于 Bean 的初始化和銷毀階段,開發(fā)人員可以通過它們實(shí)現(xiàn)一些定制化的處理邏輯。
顧名思義,InitializingBean 用于初始化 Bean,接口定義如下:
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
我們看到 InitializingBean 接口只有一個(gè)方法,即 afterPropertiesSet。從命名上看,這個(gè)方法應(yīng)該作用于屬性被設(shè)置之后。也就是說,這個(gè)方法的初始化會(huì)晚于屬性的初始化。
實(shí)際上,InitializingBean 只是 Spring 初始化時(shí)可以采用的其中一個(gè)擴(kuò)展點(diǎn)。與 InitializingBean 類似的一種機(jī)制是 InitMethod。我們知道在 Spring 中可以配置 Bean 的 init-method 屬性,具體使用方式是這樣的:
<bean class="com.xiaoyiran.springinitialization. TestInitBean" init-method="initMethod"></bean>
這兩種 Spring 初始化擴(kuò)展機(jī)制都非常常見,我們?cè)陂喿x Dubbo、MyBatis、Spring Cloud 等框架源碼時(shí)會(huì)經(jīng)常遇到。那么,這里就有一個(gè)問題,既然它們都能對(duì)初始化過程做一定的控制,執(zhí)行順序是怎么樣的呢?我們通過一個(gè)示例來分析各個(gè)機(jī)制的執(zhí)行順序,示例代碼如下所示:
public class TestInitBean implements InitializingBean {
public TestInitBean (){
System.out.println("constructMethod");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet");
}
public void initMethod() {
System.out.println("initMethod");
}
}
上述示例的執(zhí)行結(jié)果如下所示:
constructMethod
afterPropertiesSet
initMethod
顯然,基于以上結(jié)果,我們可以得出這三者生效的先后順序。
圖 2 三種初始化方法的執(zhí)行順序
結(jié)論已經(jīng)有了,我們簡(jiǎn)單地對(duì)這個(gè)結(jié)論做一個(gè)源碼分析。在 Spring 中,我們找到 AbstractAutowireCapableBeanFactory 的 initializeBean 方法,這個(gè)方法完成了我們提到的相關(guān)操作。在表現(xiàn)形式上,我們對(duì)這個(gè)方法上做一些簡(jiǎn)化,可以得到這樣的代碼結(jié)構(gòu):
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
//執(zhí)行 Aware 方法
invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
//在初始化之前執(zhí)行 PostProcessor 方法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
//執(zhí)行初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
//在初始化之后執(zhí)行 PostProcessor 方法
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;
}
你可以看一下圖 3,是這段代碼的執(zhí)行流程。我們先執(zhí)行 Aware 方法,然后在初始化之前執(zhí)行 PostProcessor 方法,接著執(zhí)行初始化方法,最后在初始化之后執(zhí)行 PostProcessor 方法。
圖 3 initializeBean 執(zhí)行流程
我們來看這里的 invokeInitMethods 方法。從命名上看,這個(gè)方法的作用是調(diào)用一批初始化方法,我們繼續(xù)對(duì)這個(gè)方法的代碼結(jié)構(gòu)做一些簡(jiǎn)化調(diào)整,以便我們理解。
protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable {
boolean isInitializingBean = (bean instanceof InitializingBean);
//判斷是否實(shí)現(xiàn) InitializingBean 接口
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
//直接調(diào)用 afterPropertiesSet 方法
((InitializingBean) bean).afterPropertiesSet();
}
if (mbd != null) {
String initMethodName = mbd.getInitMethodName();
if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&!mbd.isExternallyManagedInitMethod(initMethodName)) {
//執(zhí)行自定義的 init-method
invokeCustomInitMethod(beanName, bean, mbd);
}
}
}
可以看到,這里我們首先判斷當(dāng)前 Bean 是不是一個(gè) InitializingBean 接口的實(shí)例,如果是就直接調(diào)用它的 afterPropertiesSet 方法,我們?cè)俑鶕?jù) Bean 的定義獲取它的 init-method 屬性,如果設(shè)置了這個(gè)屬性,那么就調(diào)用一個(gè) invokeCustomInitMethod 方法。這個(gè)方法會(huì)找到 init-method 屬性并執(zhí)行指定的方法。因?yàn)樵诖a執(zhí)行流程上的前后順序,決定了 afterPropertiesSet 方法在 init-method 之前被觸發(fā)。
Aware 機(jī)制
我們?cè)谇懊娴膱?zhí)行流程圖中還看到了一個(gè) invokeAwareMethods 方法。這個(gè) invokeAwareMethods 就涉及了接下來我們要介紹的 Spring 中提供的 Aware 系列擴(kuò)展機(jī)制。
在 Spring 中,Aware 接口是一個(gè)空接口,但卻有一大批直接或間接的子接口。我們以常見的 ApplicationContextAware 接口為例來說明問題。
public interface ApplicationContextAware extends Aware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
ApplicationContextAware 的使用方法非常簡(jiǎn)單,我們可以直接使用它提供的 setApplicationContext 方法,把傳入的 ApplicationContext 暫存起來使用。通過這種方法,我們就可以獲取上下文對(duì)象 ApplicationContext。一旦獲取了 ApplicationContext,那么我們就可以對(duì) Spring 中所有的 Bean 進(jìn)行操作了。
圖 4 ApplicationContextAware 的效果圖
事實(shí)上,各種 Aware 接口中都只有一個(gè)類似 setApplicationContext 的 set 方法。如果一個(gè) Bean 想要獲取并使用 Spring 容器中的相關(guān)對(duì)象,我們就不需要再次執(zhí)行重復(fù)的啟動(dòng)過程,而是可以通過 Aware 接口提供的這些方法直接引入相關(guān)對(duì)象即可。
Dubbo 基于啟動(dòng)擴(kuò)展點(diǎn)集成 Spring 原理分析
了解了 Spring 內(nèi)置的系統(tǒng)初始化方法和 Aware 機(jī)制,接下來我們基于具體的開源框架分析一下,怎么和 Spring 完成啟動(dòng)過程的無縫集成。今天,我們討論的對(duì)象是非常經(jīng)典的分布式服務(wù)框架 Dubbo。
Dubbo 服務(wù)器端啟動(dòng)過程分析
在 Dubbo 中,負(fù)責(zé)執(zhí)行服務(wù)器端啟動(dòng)的是 ServiceBean。我們先來看 ServiceBean 這個(gè) Bean 的類定義以及主體代碼結(jié)構(gòu)。
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware {
public void afterPropertiesSet() {}
...
public void setApplicationContext(ApplicationContext applicationContext) {}
...
public void setBeanName(String name) {}
...
public void onApplicationEvent(ApplicationEvent event) {}
...
public void destroy() {}
}
可以看到 ServiceBean 實(shí)現(xiàn)了 Spring 的 InitializingBean、DisposableBean ApplicationContextAware 和 ApplicationListener 等接口,重寫了 afterPropertiesSet、destroy、setApplicationContext、onApplicationEvent 等方法。這些方法就是 Dubbo 和 Spring 整合的關(guān)鍵,我們?cè)谧约簩?shí)現(xiàn)與 Spring 框架的集成時(shí),通常也會(huì)使用這些方法。
我們先來關(guān)注一下 ServiceBean 中實(shí)現(xiàn) InitializingBean 接口的 afterPropertiesSet 方法,這個(gè)方法非常長(zhǎng),但結(jié)構(gòu)并不復(fù)雜,你可以看一下這個(gè)方法的結(jié)構(gòu)。
public void afterPropertiesSet(){
getProvider();
getApplication()
getModule();
getRegistries();
getMonitor();
getProtocols();
getPath();
if (!isDelay()) {
export();
}
}
這個(gè)代碼結(jié)構(gòu)中的 getProvider、getApplication、getModule、getMonitor 等方法的執(zhí)行邏輯和流程基本一致。以 getProvider 方法為例,Dubbo 首先會(huì)從配置文件中讀取 dubbo:provide 配置項(xiàng)。顯然,Provider、Application 和 Module 在 Dubbo 中應(yīng)該只能出現(xiàn)一次。通過執(zhí)行上述的 afterPropertiesSet 方法,相當(dāng)于在 Dubbo 框架啟動(dòng)的同時(shí),執(zhí)行了 Spring 容器的初始化過程,并把從這里獲取的一組 Dubbo 對(duì)象加載到了 Spring 容器中。
而 ServiceBean 也實(shí)現(xiàn)了 Spring 的 ApplicationContextAware 接口,所以我們不難想象存在這樣的 setApplicationContext 方法。
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
SpringExtensionFactory.addApplicationContext(applicationContext);
}
可以看到,Dubbo 通過這個(gè)方法獲取了 ApplicationContext,然后通過自己的 SpringExtensionFactory 工廠類把上下文對(duì)象保存到了框架內(nèi)部,方便后續(xù)使用。
Dubbo 客戶器端啟動(dòng)過程分析
有了 Dubbo 服務(wù)器端的基礎(chǔ)后,我們?cè)倏?Dubbo 的客戶端就會(huì)變得更簡(jiǎn)單明了。Dubbo 的客戶端啟動(dòng)方法需要參考 ReferenceBean 類。
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {
}
可以看到,ReferenceBean 實(shí)現(xiàn)了 Spring 提供的 FactoryBean、ApplicationContextAware、InitializingBean 和 DisposableBean 這四個(gè)擴(kuò)展點(diǎn)。這次,我們先挑最簡(jiǎn)單的進(jìn)行介紹。FactoryBean 和 ApplicationContextAware 接口實(shí)現(xiàn)非常簡(jiǎn)單,setApplicationContext 方法只是把傳入的 applicationContext 同時(shí)保存在 ReferenceBean 內(nèi)部以及 SpringExtensionFactory 中。
接下來,我們看一下 InitializingBean 接口的 afterPropertiesSet 方法,這個(gè)方法同樣非常長(zhǎng),但結(jié)構(gòu)也不復(fù)雜,而且與 ServiceBean 中的 afterPropertiesSet 方法結(jié)構(gòu)比較對(duì)稱。這里,我們不再給出這個(gè)方法的主體代碼結(jié)構(gòu),而是直接來到末端,找到方法中最核心的代碼。
getObject();
這個(gè) getObject 方法,實(shí)際上并不是 ReferenceBean 自身的代碼,而是實(shí)現(xiàn)了 FactoryBean 接口的同名方法。這里的 FactoryBean 接口,我們前面沒有介紹過,你可以看一下它的定義:
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
實(shí)際上,F(xiàn)actoryBean 是 Spring 框架中非常核心的一個(gè)接口,負(fù)責(zé)從容器中獲取具體的 Bean 對(duì)象。我們重點(diǎn)來看 ReferenceBean 中的 getObject 方法,這個(gè)方法又調(diào)用了 ReferenceBean 的父類 ReferenceConfig 中的 get 方法。
public synchronized T get() {
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
if (ref == null) {
init();
}
return ref;
}
很明顯,這里的核心應(yīng)該是 init 方法。這個(gè) init 方法和 ServiceConfig 中的 export 方法一樣,做了非常多的準(zhǔn)備和校驗(yàn)工作,最終得到了這行代碼。
ref = createProxy(map);
顧名思義,用 createProxy 方法來創(chuàng)建代理對(duì)象;通過代理對(duì)象,客戶端訪問遠(yuǎn)程服務(wù)就像在調(diào)用本地方法一樣。到這里,Dubbo 客戶端啟動(dòng)過程也介紹完畢。
總結(jié)
作為系統(tǒng)啟動(dòng)和初始化相關(guān)的常見擴(kuò)展點(diǎn),今天我們介紹的 InitializingBean 接口和 Aware 系列接口可以說應(yīng)用非常廣泛。我們發(fā)現(xiàn)主流的 Dubbo、MyBatis 等框架都是基于這些擴(kuò)展性接口完成與 Spring 框架的整合的。如果我們需要實(shí)現(xiàn)與 Spring 框架的集成和擴(kuò)展,這些接口是必須要掌握的內(nèi)容。建議你在日常開發(fā)過程中,多關(guān)注這些接口的應(yīng)用場(chǎng)景和方式,并根據(jù)情況集成到自己的代碼當(dāng)中。