Spring 框架和 Tomcat容器擴展接口揭祕

在 Spring 框架中,每個應用程序上下文(ApplicationContext)管理著一個 BeanFactory,BeanFactory 主要負責 Bean 定義的保存、Bean 的創建、Bean 之間依賴的自動注入等。應用程序上下文則是對 BeanFactory 和 Bean 的生命週期中的各個環節進行管理,並且提供擴展接口允許用戶對 BeanFactory 和 Bean 的各個階段進行定製,本文從以下三個點進行切入講解。

  • refresh()是應用上下文刷新階段。
  • getBean()是容器啟動後從 BeanFactory 獲取 Bean 過程。
  • close()是銷燬應用程序上下文階段。


refresh 階段


應用程序上下文刷新操作最終調用的是 AbstractApplicationContext 的 refresh 方法,其核心執行步驟如下圖所示。

Spring 框架和 Tomcat容器擴展接口揭秘


無論是解析 XML 作為 Bean 來源的 ClassPathXmlApplicationContext 還是基於掃描註解類作為 Bean 來源的 AnnotationConfigApplicationContext,在刷新上下文的過程中最終都會走這個流程,不同在於這兩者覆蓋的該流程中的一些方法可能會有不同,其實這個屬於設計模式裡面的模板模式。

獲取 BeanFactory


如上圖中,步驟(1)獲取一個 BeanFactory,對應 ClassPathXmlApplicationContext 應用程序上下文來說,這個步驟首先創建了一個 DefaultListableBeanFactory,然後解析配置 Bean 的 XML,並把 Bean 定義註冊到 BeanFactory,內部主要函數為 refreshBeanFactory,代碼如下。

 protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
} try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory;
}
} catch (IOException ex) {
... }
}


可以通過覆蓋該步驟內的 refreshBeanFactory 方法,實現自己的 BeanFactory 創建和解析配置文件的 Bean 的策略。

標準初始化配置 BeanFactory


步驟(2)配置步驟(1)創建的 BeanFactory,比如設置 BeanFactory 工廠創建 Bean 時使用什麼樣的類加載器,默認情況下使用線程上下文類加載器(默認為 AppClassLoader)。

這裡如果想實現不同的 BeanFactory 創建 Bean,使用不同的 classloader 來實現模塊隔離,可以通過在不同的 ClassPathXmlApplicationContext 上調用 setClassLoader 方法來設置不同的 classloader 來實現。

另外步驟(2)還向 BeanFactory 添加了一個 BeanPostProcessor 的實現類 ApplicationContextAwareProcessor,這個後面會講到,代碼如下。

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.setBeanClassLoader(getClassLoader());
...
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
...
}


自上下文對 BeanFactory 進行個性化定製的擴展


步驟(3)是在步驟(2)對 BeanFactory 進行標準初始化配置後,留出的允許子上下文對 BeanFactory 進行個性化定製的擴展,這時候會加載所有的 Bean 的定義,但是這時候還沒有 Bean 被實例化,這時允許註冊一些 BeanPostProcessors 類型的 Bean 用來在 Bean 初始化前後做一些事情。

例如 XmlWebApplicationContext 上下文裡面的 postProcessBeanFactory 的實現,代碼如下。

 @Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
...
}


註冊了 ServletContextAwareProcessor,用來把 servletContext 設置到實現了 ServletContextAware 接口的 Bean。

用戶註冊 BeanFactoryPostProcessor 用來對 BeanFactory 進行擴展

步驟(4)執行用戶註冊的 BeanFactoryPostProcessor 擴展 Bean,用來對 BeanFactory 中的 Bean 定義進行修改,比如常見的是統一設置某些 Bean 的屬性變量值。那麼 BeanFactoryPostProcessor 為何物呢?

public interface BeanFactoryPostProcessor { void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}


如上代碼 BeanFactoryPostProcessor 是一個接口,有一個方法,該方法參數是 beanFactory,由於通過 beanFactory 可以訪問所有的 Bean 的定義,所以當我們實現了該接口,並注入實現類到 Spring 容器後,就可以在實例化 Bean 前對指定的 Bean 定義進行修改或者註冊新的 Bean。

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

如上代碼,BeanDefinitionRegistryPostProcessor 接口繼承自 BeanFactoryPostProcessor,它新添加了一個接口,用來在BeanFactoryPostProcessor 實現類中 postProcessBeanFactory 方法執行前再註冊一些 Bean 到 beanFactory 中。

基礎知識普及完畢後,下面來看步驟(4)做了什麼?

步驟(4) 首先執行實現了 BeanDefinitionRegistryPostProcessor 接口的 Bean 的 postProcessBeanDefinitionRegistry 方法,然後再執行實現了 BeanFactoryPostProcessor 接口的 Bean 的 postProcessBeanFactory 方法。由於接口的實現類可能會有多個,如果你想先執行某些接口的方法,可以通過實現 PriorityOrdered 或者 Ordered 接口給每個接口定義一個優先級,另外實現 PriorityOrdered 接口的優先級大於實現 Ordered 的優先級。

比如,基於掃描註解類作為 Bean 來源的 AnnotationConfigApplicationContext,會在 refresh 階段前註冊一個ConfigurationClassPostProcessor,它實現了 BeanDefinitionRegistryPostProcessor、PriorityOrdered 兩個接口。

因為實現了第一接口,所以會在步驟(4)的時候執行 postProcessBeanDefinitionRegistry 方法,這個方法內部作用是使用ConfigurationClassParser 解析所有標註有 @Configuration 註解的類,並解析該類裡面所有標註 @Bean 的方法和標註 @Import 的bean,並注入這些解析的 Bean 到 Spring上下文容器裡面。

因為實現了第二個接口,所以該類有 getOrder 方法返回該類的優先級,這裡實現為O rdered.LOWEST_PRECEDENCE,也就是優先級最低。

比如解析 ${...}佔位符的 PropertyPlaceholderConfigurer 會在步驟(4)階段執行 postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 方法對 Bean 定義的屬性值中 ${...} 進行替換,具體一個例子如下。



config.properties


UTF-8


如上代碼,首先注入了 propertyConfigurer 實例並且配置了屬性值來源為 config.properties,並且在注入 TestImpl 實例的時候使用了佔位符 "${name}" 來設置 name 屬性,其中 config.properties 內容如下:

name=jiaduo

其中 TestImpl 代碼如下:

public class TestImpl { private String name; public String getName() { return name;
} public void setName(String name) { this.name = name;
} public void say(){
System.out.println("hello " +name);
}
}

那麼當我們通過運行以下代碼:

 ClassPathXmlApplicationContext cpxa = new ClassPathXmlApplicationContext("bean.xml");
cpxa.getBean("testPlaceholder",TestImpl.class).say();

會輸出以下結果:

hello jiaduo


佔位符替換的時機就是在步驟(4)執行 PropertyPlaceholderConfigurer 類的 postProcessBeanFactory 方法時候,該方法用 config.properties文件 中 key 為 name 的屬性值替換 BeanFactory 裡面 Bean 的屬性值為 "${name}"的屬性。需要注意的是這時候 Bean 還沒有被實例化,只是靜態的進行屬性值替換。

小結:BeanFactoryPostProcessor 後置處理器擴展接口是在 Bean 進行實例化前執行的,它的作用是對 BeanFactory 中 Bean 的定義做修改(比如新增 Bean 的定義,修改已有 Bean 定義,修改 Bean 的屬性值等)。

註冊 BeanPostProcessor 到 BeanFactory 的 beanPostProcessors 列表

相比 BeanFactoryPostProcessor 是在 Bean 實例化前對 BeanFactory 進行擴展,BeanPostProcessor 是在 Bean 實例化後對 Bean 進行擴展,下面看看 BeanPostProcessor 的接口定義,代碼如下。

public interface BeanPostProcessor { //在Bean實例化後,初始化前進行一些擴展操作
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean;
} //在Bean實例化後,初始化後進行一些擴展操作
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean;
}
}

本階段就是把用戶註冊的實現了該接口的 Bean 進行收集,然後放入到 BeanFactory 的 beanPostProcessors 屬性裡面,待後面使用。

為應用上下文子類初始化一些特殊類留出的擴展


refresh 核心執行步驟(6)是為應用上下文子類初始化一些特殊類留出的擴展,例如 SpringBoot 中 AbstractApplicationContext 的子類 EmbeddedWebApplicationContext 應用程序上下文,重寫的 onRefresh 方法如下:

 protected void onRefresh() { super.onRefresh(); try {
createEmbeddedServletContainer();
} catch (Throwable ex) { throw new ApplicationContextException("Unable to start embedded container",
ex);
}
}

如上代碼在重寫的 onRefresh 方法內創建了內嵌 Web 容器。

加Java架構師進階交流群獲取Java工程化、高性能及分佈式、高性能、深入淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大數據等多個知識點高級進階乾貨的直播免費學習權限 都是大牛帶飛 讓你少走很多的彎路的 群號是:338549832 對了 小白勿進 最好是有開發經驗

注:加群要求

1、具有工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。

2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。

3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。

4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。

5.阿里Java高級大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!


分享到:


相關文章: