本文從幾個問題:Spring框架有哪幾個核心組件?爲什麼需要這些組件?他們又是如何結合在一起構成Spring的骨骼架構?Spring的AOP特性是如何利用這些基礎的骨骼架構老工作的?來探討Spring框架的設計理念。從而讓我們後面學習Spring的應用更容易一些。
1 Spring的骨骼架構
在Spring框架中,核心組件有三個:Core、Context和Bean。他們構建起了整個Spring的骨骼架構,沒有他們就不可能有AOP、Web等上層的特性功能。
1.1 Spring的設計理念
在上面提到的三個核心組件中,Bean是核心中的核心。因爲Spring就是面向Bean的編程,Bean在Spring中才是真正的主角。
Spring之所以如此流行,是因爲它解決了一個問題,它可以讓你把對象之間的依賴關係轉而用配置文件或註解來管理,也就是它的依賴注入機制。而這個注入關係在一個叫Ioc的容器中管理。Ioc容器中就是被Bean包裹的對象。Spring正是通過把對象包裹在Bean中從而達到管理這些對象及做一系列額外操作的目的。
它的設計理念就是構建一個數據結構,然後根據這個數據結構設計它的生存環境,並讓它在這個環境中按照一定的規律不停地運動,在它們的不停運動中設計一個系列與環境或者與其他個體完成信息交換。
1.2 核心組件如何協同工作
那麼在Spring的骨骼架構中,這三個組件又是怎麼工作的呢?它們分別扮演什麼樣的角色?
舉個栗子,如果把Bean比作一場演出中的演員,那麼Context就是這場演出的舞臺背景,而Core就是演出的道具了。它們三個放在一切就具備了演一場好戲的最基本的條件。而要想演出更加精彩,還需要它表演的節目足夠精彩,這些節目就是Spring能夠提供的特色功能了。
Context要做的就是發現每個Bean之間的關係,爲它們建立這種關係並且維護好這種關係。所以,Context就是一個Bean關係的集合,又叫做Ioc容器,一旦建立起這個Ioc容器,Spring就可以工作了。
而Core就是發現、建立和維護每個Bean之間的關係所需要的一系列工具。相當於一個Util,爲Context所用。
2 核心組件詳解
2.1 Bean組件
Bean組件在Spring的org.springframework.beans包下,這個包下的類主要解決了3個問題:Bean的定義、Bean的創建和Bean的解析。對於使用者來說,唯一需要關心的就是Bean的創建,其他兩個由Spring在內部幫你完成。
Bean的定義:
Bean的定義主要由BeanDefinition描述。
Bean的定義完整的描述了在Spring的配置文件中你定義的<bean/>
節點後中所有的信息,包括各種子節點。當Spring成功解析你定義的一個<bean/>
節點後,在Spring內部他就被轉換成BeanDifinition對象,以後所有的操作都是對這個對象進行的。
Bean的創建:
Bean的創建時典型的工廠模式。它的頂級接口是BeanFactory。
其最終的實現類是DefaultListableBeanFactory。至於爲什麼要定義這麼多層次的接口,主要是爲了區分在Spring內部對象的傳遞和轉化過程中,對對象的數據訪問所做的限制。例如ListableBeanFactory接口表示這些Bean是列表的,而HierarchicalBeanFactory表示這些Bean是由繼承關係的,也就是每個Bean有可能有父Bean,AutowriteCapableBeanFactory接口定義Bean的自動裝配規則。
Bean的解析:
Bean的解析過程非常複雜,主要就是對Spring配置文件的解析,這個解析過程主要通過下面幾個類完成。
2.2 Context組件
Context在Spring的org.springframework.context包下,其作用前面已經說了,就是個ISpring提供一個運行時的環境,用於保存各個對象的狀態。
Context的頂級父類是ApplicationContext。
ApplicationContext的子類主要包含兩個方面:
- ConfigurableApplicationContext表示該Context是可修改的,也就是在構建Context中,用戶可以動態添加或修改已有的配置信息。
- WebApplicationContext就是爲Web準備的Context,它可直接訪問ServletContext。
總的來說ApplicationContext必須完成以下幾件事情:
- 標示一個應用環境
- 利用BeanFactory創建Bean對象
- 保存對象關係表
- 能夠捕獲各種事情
Context作爲Spring的Ioc容器,基本上整合了Spring的大部分功能。
2.3 Core組件
Core包含了很多關鍵類,一個重要的組成部分就是定義了資源的訪問方式。
下面是Resource相關的類結構圖
下面是Context和Resource如何建立關係的類關係圖。
Context把資源的加載、解析和描述工作委託給了ResourcePatternResolver類來完成,它相當於一個接頭人,它把資源的加載、解析和資源的定義整合在一起便於其他組件使用。
2.4 Ioc容器如何工作
Ioc容器實際上是Context組件結合其他兩個組件共同構建了一個Bean關係網,構建這個關係網的入口就在AbstractApplicationContext類的refresh方法中,代碼如下,這個方法就是構建整個Ioc容器過程的完整代碼。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 爲刷新準備新的context
prepareRefresh();
// 刷新所有BeanFactory子容器,當BeanFactory存在時就更新,不存在時就創建一個新的。BeanFactory的原始對象是DefaultListableBeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 爲創建好的BeanFactory添加一些Spring本身需要的工具類
prepareBeanFactory(beanFactory);
try {
// 註冊實現BeanPostProcessor接口的Bean
postProcessBeanFactory(beanFactory);
// 初始化和執行BeanFactoryPostProcessor beans
invokeBeanFactoryPostProcessors(beanFactory);
// 初始化和執行BeanPostProcessor beans
registerBeanPostProcessors(beanFactory);
// 初始化MessageSource.
initMessageSource();
// 初始化 event multicaster.
initApplicationEventMulticaster();
// 刷新由子類實現的方法.
onRefresh();
// 檢查註冊事件.
registerListeners();
// 初始化non-lazy-init單例Bean.
finishBeanFactoryInitialization(beanFactory);
// 執行LifecycleProcessor.onRefresh()和ContextRefreshedEvent事件.
finishRefresh();
}
catch (BeansException ex) {
// 銷燬beans.
destroyBeans();
cancelRefresh(ex);
throw ex;
}
}
}
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
這三行代碼對Spring的功能擴展性起了至關重要的作用。前兩行主要是讓你現在可以對已經構建的BeanFactory的配置做修改,後面一行就是讓你可以對以後再創建Bean的實例對象是添加一些自定義的操作。所以它們都擴展了Spring的功能。
其中 invokeBeanFactoryPostProcessors方法中主要是獲取實現BeanFactoryPostProcessor接口的子類,並執行它的PostProcessBeanFactory方法,方法聲明如下void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
registerBeanPostProcessors方法可以獲取用戶定義的實現了BeanPostProcessor接口的子類,並把它們註冊到BeanFactory對象中的beanPostProcessors變量中。BeanPostProcessor中聲明瞭兩個方法:postProcessBeforeInitialization和postProcessAfterInitialization,分別用於在Bean對象初始化時執行,可以執行用戶自定義的操作。
後面的幾行代碼是初始化監聽事件和對系統的其他監聽者的註冊,堅挺着必須是ApplicationListener的子類。
創建Bean並構建Bean的關係網
創建Bean是從finishBeanFactoryInitialization方法開始的。
protected void finishBeanFactoryInitialization(
ConfigurableListableBeanFactory beanFactory) {
// 不使用TempClassLoader.
beanFactory.setTempClassLoader(null);
// 禁止修改當前Bean的配置信息.
beanFactory.freezeConfiguration();
// 實例化non-lazy-init類型的bean.
beanFactory.preInstantiateSingletons();
}
從上面可以看出Bean的實例化是在BeanFactory中發生的。
public void preInstantiateSingletons() throws BeansException {
if (this.logger.isInfoEnabled()) {
this.logger.info("Pre-instantiating singletons in " + this);
}
synchronized (this.beanDefinitionMap) {
for (String beanName : this.beanDefinitionNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton()
&& !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
final FactoryBean factory =
(FactoryBean) getBean(FACTORY_BEAN_PREFIX+ beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null
&& factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(
new PrivilegedAction<Boolean>() {
public Boolean run() {
return ((SmartFactoryBean) factory).isEagerInit();
}
}, getAccessControlContext());
}
else {
isEagerInit = factory instanceof SmartFactoryBean
&& ((SmartFactoryBean) factory).isEagerInit();
}
if (isEagerInit) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
}
}
}
注意這裏的FactoryBean,這是一個非常重要的Bean,Spring有一大半的擴展功能都與這個Bean有關,這是一個特殊的Bean。它是以工廠Bean,可以產生Bean的實例的Bean。如果一個類繼承FactoryBean,用戶就可以自己定義生產實例對象的方法,而這一切,只需要實現它的getObject()方法即可。
如何創建Bean的實例對象及如何構建Bean實例對象之間的關聯關係是Spring中的一個核心。下面是這個過程的流程圖
如果是普通的Bean就通過調用getBean方法直接創建它的實例。
Ioc容器的擴展點
對Spring的Ioc容器來說,主要有BeanFactoryPostProcessor和BeanPostProcessor,他們分別在構建BeanFactory和構建Bean對象是調用。還有就是InitializingBean和DisposableBean,它們分別在Bean實例創建和銷燬時被調用。還有一個是FactoryBean,它是一個特殊的Bean,這個Bean可以被用戶更多的控制。
下面這個比喻可以讓我們更好的理解他們之間的關係:
把 Ioc 容器比作一個箱子,這個箱子裏有若干個球的模子,可以用這些模子來造很多種不同的球,還有一個造這些球模的機器,這個機器可以產生球模。那麼他們的對應關係就是 BeanFactory 就是那個造球模的機器,球模就是 Bean,而球模造出來的球就是 Bean 的實例。那前面所說的幾個擴展點又在什麼地方呢? BeanFactoryPostProcessor 對應到當造球模被造出來時,你將有機會可以對其做出設當的修正,也就是他可以幫你修改球模。而 InitializingBean 和 DisposableBean 是在球模造球的開始和結束階段,你可以完成一些預備和掃尾工作。BeanPostProcessor 就可以讓你對球模造出來的球做出適當的修正。最後還有一個 FactoryBean,它可是一個神奇的球模。這個球模不是預先就定型了,而是由你來給他確定它的形狀,既然你可以確定這個球模型的形狀,當然他造出來的球肯定就是你想要的球了,這樣在這個箱子里尼可以發現所有你想要的球。
我們使用Spring必須先構建Ioc容器,沒有他Spring無法工作,ApplicationContext.xml就是Ioc容器的默認配置文件,Spring的所有特性都是基於Ioc容器工作的,比如後面介紹的AOP。
Ioc實際上爲你構建了一個魔方,Spring爲你搭好了骨骼架構,這個魔方到底能變出什麼好東西,這必須要有你的參與——通過實現擴展點來改變Spring的通用行爲。AOP的實現就是Spring本身實現了其擴展點達到了它想要的特性功能。
3 Spring中的AOP特性
3.1 動態代理
Spring的AOP是基於動態代理實現的。在JDK的java.lang.reflect包下有一個Proxy類,它正是構建代理類的入口。調用其newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法就可以創建代理對象。
ClassLoader,用於加載代理類的Loader類,通常這個Loader和被代理的類是同一個Loader類;Interfaces,是要被代理的那些接口;InvocationHandler用於執行除了被代理接口中方法之外的用戶自定義的操作,他也是用戶需要dialing的最終目的。用戶調用目標方法都被代理到在InvocationHandler 類中定義的唯一方法invoke()中。
3.2 Spring AOP的實現
代理的目的是調用目標方法時可以轉而執行InvocationHandler 類的invoke方法。
要實現代理類,在Spring的配置文件中通常是這樣定義一個Bean的:
<bean id="testBeanSingleton"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>
org.springframework.aop.framework.PrototypeTargetTests$TestBean
</value>
</property>
<property name="target"><ref local="testBeanTarget"></ref> </property>
<property name="singleton"><value>true</value></property>
<property name="interceptorNames">
<list>
<value>testInterceptor</value>
<value>testInterceptor2</value>
</list>
</property>
</bean>
可以看到,要設置被代理的接口和接口的實現類,以及攔截器(在執行目標方法之前被調用)。
從這個代理類可以看出它繼承了FactoryBean的ProxyFactoryBean。FactoryBean之所以特別就是在於它可以讓你自定義對象的創建方法。當然這裏的代理對象要通過Proxy類來生成。
在Spring創建了代理對象之後,當你調用目標對象上的方法時,都會被代理到InvocationHandler 類的invoke方法中執行,這在前面已經解釋過了。
由於版本更新的問題,文章中某些地方可能和現在的版本不一樣,但總體的框架是不會變太多的,本文的用意也是通過對Spring框架的底層實現有一個總體的把握,從而讓我們對後面Spring的學習可以有一個清晰的脈絡
補充(2017-3-18):spring 容器的初始化
Spring框架實現了servlet的一個監聽器——ServletContextListener(Servlet容器初始化時觸發,在所有的Filter和Servlet的init方法調用之前contextInitalized接口先被調用)。Spring對應的實現類是org.springframework.web.context.ContextLoaderListener(因爲是監聽器,所以需要在web.xml中的<listener>
標籤中定義)。當Servlet容器加載時啓動Spring容器。ContextLoaderListener在contextInitalized方法中初始化Spring容器,有幾種方法可以加載Spring容器,通過在web.xml的<context-param>
標籤中哦誒之Spring的applicationContext.xml路徑,文件名可以任意取,如果沒有配置,將在/WEB-INF/路徑下查找默認的applicationContext.xml文件。
本文爲讀書筆記,主要參考自許令波的《深入分析Java Web技術內幕》一書中的“Spring框架的設計理念與設計模式分析”一節。大家有興趣的可以拜讀一下。此篇文章,作者在gitHub上也有分享,地址是:Spring 框架的設計理念與設計模式分析