手把手教你實現spring-context
系列文章
手把手教你實現spring-beans (一)
手把手教你實現spring-beans (二)
手把手教你實現spring-context
手把手教你實現spring-aop (TODO)
前言
本文是對tiny-spring專案的詳細解讀,聚焦spring-context的基本實現,對應著(seventh~ninth)-stage
這三個構建過程。
引入ResourceLoader
Spring提供的ApplicationContext
在BeanFactory
的基礎之上,添加了幾個重要特性:資源載入、事件派發和國際化。不僅如此,相較於BeanFactory
只能手動註冊服務,ApplicationContext
BeanPostProcessor
和BeanFactoryPostProcessor
,並且ApplicationContext
在建立時就會一次性載入所有non lazy init
的JavaBean
,可以說ApplicationContext
將BeanFactory
提高了一個臺階,更易於客戶端程式的使用。
讓我們先來看看增強的資源載入功能。在第一篇中我們說到Spring提供了一個對資源的統一抽象——Resource
,並由此衍生出多個代表不同來源的Resource
實現。然而,在基礎BeanFactory
的使用過程中,具體使用什麼型別的Resource
是需要客戶端程式手動指定的:
Resource resource = new ClassPathResource("test/config.xml");
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(resource);
複製程式碼
比如這裡,我們選擇使用ClassPathResource
ResourceLoader
。實際上,Spring還提供了一個ResourceLoader
的子介面ResourcePatternResolver
,它可以一次性載入多個資源。
// 命令列輸入 git checkout seventh-stage,在io包中可以看到對ResourceLoader的定義
/**
* 策略介面,用來載入資源。
* Discussion: 可以根據location的不同表現形式,
* 返回不同的Resource,故而稱為策略介面。
*/
public interface ResourceLoader {
/**
* 載入一個資源,可以是ClassPathResource/FileSystemResource等等。
*/
Resource getResource(String location);
/**
* 返回ResourceLoader在載入資源時使用的類載入器。
*/
ClassLoader getClassLoader();
}
複製程式碼
有了ResourceLoader
,我們只需要提供資源的地址,至於它到底是什麼型別的資源Spring會幫我們做判斷,而不再需要我們手動指定。DefaultResourceLoader
是一個ResourceLoader
的實現,它根據資源地址中的協議型別來判斷具體的資源型別,程式碼比較簡單,這裡就不多說了。
引入事件機制
BeanFactory
為其持有的各種JavaBean
提供了一套統一的生命週期管理(init-method
、InitializingBean
,etc),但客戶端程式卻沒有多少手段可以干涉到BeanFactory
本身。為此ApplicationContext
提供了一套事件機制,用以向客戶端程式報告ApplicationContext
的程式,當然也支援派發自定義事件。實際上ApplicationContext
還實現了Lifecycle
介面,客戶端程式可以依此來開啟或關閉ApplicationContext
,不過在我們的實現中並沒有提供這一層抽象。
ApplicationContext
中的事件處理是由ApplicationEvent
類和ApplicationListener
介面來提供的,兩者分別基於標準的java.util.EventObject
和java.util.EventListener
,事件的派發則是通過ApplicationEventPublisher
介面。Spring中事件派發機制使用觀察者模式來實現。
/**
* 應用程式事件派發器,封裝了事件的派發邏輯,一般作為
* ApplicationContext的父介面使用。
*/
public interface ApplicationEventPublisher {
/**
* 派發一個ApplicationEvent,並通知所有已註冊的ApplicationListener。
*/
void publishEvent(ApplicationEvent event);
}
複製程式碼
為了支援ApplicationEventPublisher
的工作,Spring又提供了ApplicationEventMulticaster
介面,它支援對ApplicationListener
的加入、移除管理,還可以將收到的ApplicationEvent
派發到已註冊的ApplicationListener
,可以說ApplicationEventMulticaster
就是ApplicationEventPublisher
的一個代理。
/**
* 應用程式事件多播器,管理著一組ApplicationListener,並將
* ApplicationEvent派發給它們。
*
* ApplicationEventPublisher就是利用ApplicationEventMulticaster
* 來將事件派發給監聽器。
*/
public interface ApplicationEventMulticaster {
/**
* 新增一個ApplicationListener來接收ApplicationEvent。
*/
void addApplicationListener(ApplicationListener listener);
/**
* 移除一個ApplicationListener。
*/
void removeApplicationListener(ApplicationListener listener);
/**
* 移除所有已註冊的ApplicationListener。
*/
void removeAllListeners();
/**
* 將給定的ApplicationEvent多播到適當的ApplicationListener(s)。
*/
void multicastEvent(ApplicationEvent event);
}
複製程式碼
DefaultApplicationEventMulticaster
實現了ApplicationEventMulticaster
介面,非常簡單的一個類,各位同學可以自行翻閱。
引入ApplicationContext
如果拋開國際化不談,講到這裡ApplicationContext
的定義也就呼之欲出了:
/**
* 在BeanFactory之上整合許多易用的功能,更加方便客戶端的使用,比如資源載入、事件派發等等。
*/
public interface ApplicationContext extends ResourceLoader,ListableBeanFactory,ApplicationEventPublisher {
/**
* 返回ApplicationContext的名稱
*/
String getDisplayName();
/**
* 返回ApplicationContext的啟動時間
*/
long getStartupDate();
}
複製程式碼
和上一篇提到的BeanFactory
的設計思路類似,ApplicationContext
也採用了分層的設計思想,其中一個比較重要的子介面是ConfigurableApplicationContext
:
/**
* 類似於BeanFactory設計,ApplicationContext也只是一個功能最小的介面,
* ConfigurableApplicationContext在此基礎之上擴充了它的功能。
*/
public interface ConfigurableApplicationContext extends ApplicationContext {
/**
* 新增一個BeanFactory後置處理器。
*/
void addBeanFactoryPostProcessor(BeanFactoryPostProcessor beanFactoryPostProcessor);
/**
* 新增一個ApplicationListener用來接收ApplicationContext的開啟、關閉、重新整理等事件。
*/
void addApplicationListener(ApplicationListener listener);
/**
* 重新整理ApplicationContext。
*/
void refresh() throws BeansException,IllegalStateException;
/**
* 關閉當前ApplicationContext。
*/
void close();
/**
* 返回當前ApplicationContext內部組合的BeanFactory。
*/
ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
}
複製程式碼
實現ApplicationContext
AbstractApplicationContext
是Spring提供的一個模板類,它實現了ConfigurableApplicationContext
的大部分基礎功能。具體的,AbstractApplicationContext
選擇繼承DefaultResourceLoader
從而獲得了資源載入的能力,同時通過ConfigurableApplicationContext.getFactory()
返回的ConfigurableListableBeanFactory
滿足了對ListableBeanFactory
的需求。這樣的設計,使得AbstractApplicationContext
成功的將BeanFactory
的建立工作留給了子類。
前面說過,ApplicationContext
在啟動時會一次性初始化所有non lazy init
的JavaBean
,並且可以自動發現和註冊服務,這又是怎麼做到的呢?這一切的核心在於refresh()
方法的實現,我們來具體看一下:
// 命令列輸入 git checkout eighth-stage
@Override
public void refresh() throws BeansException,IllegalStateException {
// 記錄啟動時間
startupDate = System.currentTimeMillis();
// 重新整理內部BeanFactory
ConfigurableListableBeanFactory beanFactory = refreshBeanFactory();
// 為BeanFactory進行必要的準備工作
prepareBeanFactory(beanFactory);
try {
// 進行額外的後置處理
postProcessBeanFactory(beanFactory);
// 執行BeanFactoryPostProcessor的回撥
invokeBeanFactoryPostProcessors(beanFactory);
// 註冊所有BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 初始化事件多播器
initApplicationEventMulticaster();
// 準備完成,正在重新整理
onRefresh();
// 註冊所有ApplicationListener
registerApplicationListeners();
// 完成BeanFactory的初始化流程
finishBeanFactoryInitialization(beanFactory);
// 完成重新整理
finishRefresh();
} catch (BeansException e) {
// 若失敗,就清理資源
beanFactory.destroySingletons();
// 丟擲這個異常給呼叫者
throw e;
}
}
複製程式碼
其中,refreshBeanFactory()
是一個模板方法,顯然我們應該在這裡重新建立BeanFactory
並載入配置檔案,又因為refresh
方法可以多次呼叫,所以我們也應該清理一下原來BeanFactory
持有的資源(主要是快取的singleton bean)。同時為了給子類留下進一步的定製空間,又定義了鉤子方法prepareBeanFactory(...)
和postProcessBeanFactory(...)
。提一下與ApplicationContext
有關的幾個Aware
介面(ResourceLoaderAware
,ApplicationContextAware
,ApplicationEventPublisherAware
),它們由名為ApplicationContextAwareProcessor
的BeanPostProcessor
來負責處理,這個BeanPostProcessor
就是在prepareBeanFactory(...)
中註冊的。
public class ApplicationContextAwareProcessor implements BeanPostProcessor {
private final ApplicationContext applicationContext;
public ApplicationContextAwareProcessor(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public Object postProcessBeforeInitialization(Object bean,String name) throws BeansException {
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean,String name) throws BeansException {
return bean;
}
}
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 新增Bean後置處理器
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
}
複製程式碼
到這裡,可以說BeanFactory
已經完全準備完畢,是時候為BeanFactoryPostProcessor
執行回撥了。
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
// 1. 處理直接註冊的BeanFactoryPostProcessor
for (BeanFactoryPostProcessor processor : getBeanFactoryPostProcessors()) {
processor.postProcessBeanFactory(beanFactory);
}
// 2. 處理在配置檔案中配置的BeanFactoryPostProcessor
// 2.1 獲取型別為BeanFactoryPostProcessor的所有beanName
String[] beanNames = beanFactory.getBeanDefinitionNames(BeanFactoryPostProcessor.class);
for (String beanName : beanNames) {
// 2.2 在呼叫之前確保BeanFactoryPostProcessor得到初始化
BeanFactoryPostProcessor processor = getBean(beanName,BeanFactoryPostProcessor.class);
// 2.3 呼叫BeanFactoryPostProcessor的相關方法
processor.postProcessBeanFactory(beanFactory);
}
}
複製程式碼
這裡分為兩部分,首先是為直接註冊的BeanFactoryPostProcessor
執行回撥,然後再去配置檔案中尋找BeanFactoryPostProcessor
,為它們執行回撥,也就是所謂的自動發現服務。registerBeanPostProcessors(...)
同理,它在配置檔案尋找BeanPostProcessor
,並將尋找到的BeanPostProcessor
註冊進BeanFactory
:
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// ApplicationContext表面上只是一個ListableBeanFactory,// 並不具備ConfigurableBeanFactory.addBeanPostProcessor(...)的能力。
// 對ApplicationContext來說,配置檔案中配置的BeanPostProcessor就是所有的了
String[] beanNames = beanFactory.getBeanDefinitionNames(BeanPostProcessor.class);
if (beanNames.length > 0) {
for (String beanName : beanNames) {
// 例項化所有BeanPostProcessor
BeanPostProcessor processor = getBean(beanName,BeanPostProcessor.class);
// 註冊進beanFactory
beanFactory.addBeanPostProcessor(processor);
}
}
}
複製程式碼
注意這裡beanFactory.getBeanDefinitionNames(...)
是通過查詢BeanFactory
持有的BeanDefinition(s)
來判斷,並不會導致任何bean的例項化。換句話說,執行完registerBeanPostProcessors(...)
,除了BeanFactoryPostProcessor
和BeanPostProcessor
被例項化了之外,還沒有其他bean被例項化。
NOTE: Spring還額外提供了Ordered
介面來調整BeanFactoryPostProcessor
和BeanPostProcessor
的呼叫順序,tiny-spring並沒有做這些。
接下來的initApplicationEventMulticaster()
建立了ApplicationEventMulticaster
,它被用來實現ApplicationEventPublisher
。
@Override
public void publishEvent(ApplicationEvent event) {
eventMulticaster.multicastEvent(event);
}
複製程式碼
現在只要根據事件找到事件對應的監聽器,把它交給ApplicationEventMulticaster
管理,一個完整的觀察者模式就實現了。在這裡Observable
對應ApplicationEvent
,Observer
對應ApplicationListener
,事件釋出則由ApplicationEventMulticaster
來處理。onRefresh
也是一個鉤子方法,預設只是一個空的實現。
protected void registerApplicationListeners() {
// 1. 重新註冊所有手動註冊上去的ApplicationListener
manuallyRegisteredListeners.forEach(eventMulticaster::addApplicationListener);
// 2. 註冊配置檔案中定義的ApplicationListener
Collection<Object> listeners = getBeansOfType(ApplicationListener.class,true,false).values();
for (Object listener : listeners) {
eventMulticaster.addApplicationListener((ApplicationListener) listener);
}
}
複製程式碼
剩下的finishBeanFactoryInitialization(...)
實現了ApplicationContext
在啟動時一次性初始化所有non lazy init
的JavaBean
的功能:
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
beanFactory.preInstantiateSingletons();
}
複製程式碼
直接呼叫ConfigurableListableBeanFactory.preInstantiateSingletons()
,簡潔明瞭。finishRefresh()
意味著重新整理結束,因此它發出了ContextRefreshedEvent
事件通知上一步註冊的所有ApplicationListener
。
protected void finishRefresh() {
publishEvent(new ContextRefreshedEvent(this));
}
複製程式碼
除了可以重新整理,ApplicationContext
還可以在不需要時被關閉:
@Override
public void close() {
System.out.println("正在關閉" + getDisplayName());
// 銷燬所有快取的singleton bean
getBeanFactory().destroySingletons();
// 發出ContextClosedEvent
publishEvent(new ContextClosedEvent(this));
}
複製程式碼
ApplicationContext
在被關閉時會銷燬所有的singleton bean,併發出ContextClosedEvent
通知所有的ApplicationListener
。
AbstractRefreshableApplicationContext
是AbstractApplicationContext
的子類,它實現了BeanFactory
的建立和重新整理,並將配置檔案的載入留給了子類。
@Override
protected ConfigurableListableBeanFactory refreshBeanFactory() {
// 重新整理時如果BeanFactory已經存在,首先要進行資源的清理
if (beanFactory != null) {
beanFactory.destroySingletons();
beanFactory = null;
}
// 清理完畢重新建立BeanFactory並載入配置檔案
try {
DefaultListableBeanFactory listableBeanFactory = createBeanFactory();
loadBeanDefinitions(listableBeanFactory);
beanFactory = listableBeanFactory;
return listableBeanFactory;
} catch (Exception e) {
throw new ApplicationContextException("重新整理BeanFactory失敗",e);
}
}
/**
* 建立BeanFactory,可以由子類進一步定製。
*/
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory();
}
/// MARK - Template method
/**
* 將載入BeanDefinition的功能交由子類去實現,
* 子類根據資源的型別,運用不同的載入方式,從而派生出不同的ApplicationContext。
*/
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException;
複製程式碼
AbstractXMLApplicationContext
在此基礎之上實現了對配置檔案的載入但遮蔽了它的來源,ClassPathXMLApplicationContext
交代了配置檔案的來源,從而成為一個真正可用的ApplicationContext
。
BeanFactoryPostProcessor示例
在上一篇中,我們提到PropertyEditorRegistrar
,使用它可以向BeanFactory
中註冊自定義的PropertyEditor
,現在讓我們來實現它。在瞭解了ApplicationContext
的實現之後,我們知道在為BeanFactoryPostProcessor
執行回撥的時候,所有的BeanDefinition
已經載入完畢,但是還沒有任何bean得到了初始化,這是一個非常好的時機去更新BeanDefinition
。CustomEditorConfigurer
就利用了這個時機,先註冊PropertyEditor
到BeanFactory
中,後續bean的初始化過程就能用上自定義的PropertyEditor
。
// 命令列輸入 git checkout ninth-stage
public class CustomEditorConfigurer implements BeanFactoryPostProcessor {
// PropertyEditor註冊器
private PropertyEditorRegistrar[] propertyEditorRegistrars;
public void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
this.propertyEditorRegistrars = propertyEditorRegistrars;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (propertyEditorRegistrars != null) {
for (PropertyEditorRegistrar registrar : propertyEditorRegistrars) {
beanFactory.addPropertyEditorRegistrar(registrar);
}
}
}
}
複製程式碼
為了讓PropertyEditorRegistrar
和BeanFactory
產生關聯,我們需要修改一下ConfigurableBeanFactory
的定義,增加對PropertyEditorRegistrar
的管理:
/**
* 新增一個PropertyEditorRegistrar。
*/
void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar);
複製程式碼
在AbstractBeanFactory
中當然也就需要一個集合去儲存PropertyEditorRegistrar
:
// 儲存所有的PropertyEditorRegistrar
private final Set<PropertyEditorRegistrar> propertyEditorRegistrars = new HashSet<>();
@Override
public void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar) {
propertyEditorRegistrars.add(registrar);
}
複製程式碼
上一篇中也說到屬性注入最後是交給了BeanWrapper
去處理,BeanWrapper
因而需要同步BeanFactory
中的PropertyEditor
。因此我們需要修改一下AbstractAutowireCapableBeanFactory
中的同步過程,將與PropertyEditorRegistrar
關聯的PropertyEditor
也同步過去。
/**
* 將BeanFactory持有的PropertyEditor同步到BeanWrapper
*/
private void registerPropertyEditors(BeanWrapper wrapper) {
// 1. 註冊PropertyEditorRegistrar中的
for (PropertyEditorRegistrar registrar : getPropertyEditorRegistrars()) {
registrar.registerCustomEditors(wrapper);
}
// 2. 同步BeanFactory持有的
Map<Class<?>,PropertyEditor> customEditors = getCustomEditors();
Set<Class<?>> keys = customEditors.keySet();
for (Class<?> type : keys) {
wrapper.registerCustomEditor(type,customEditors.get(type));
}
}
複製程式碼
至此一切關節都一打通,test
目錄下有一個對CustomEditorConfigurer
的使用示例。
結語
ApplicationContext
介紹到這裡就差不多了,我是愛呆毛的小士郎,歡迎交流~