1. 程式人生 > >沈澱再出發:spring的架構理解

沈澱再出發:spring的架構理解

掌握 event ebe 面試 標識 sys 搭建 cancel root

沈澱再出發:spring的架構理解

一、前言

在spring之前使用的EJB框架太龐大和重量級了,開發成本很高,由此spring應運而生。關於spring,學過java的人基本上都會慢慢接觸到,並且在面試的時候也是經常遇到的,因為這個技術極大地方便了我們的開發和部署,並且由此衍生出來的框架和思想在很多地方都有使用,比如spring mvc,spring boot,spring cloud等等框架以及IoC和AOP這兩個spring最本質的思想。可以說一切的本質都是為了高內聚,松耦合,將不同的事物之間盡量的分割清楚,通過某一種額外的比較好修改的文檔來記錄這兩者的聯系,然後通過框架來將兩者在聯系起來,這樣做看似麻煩實則便於我們擴展、維護和變更,非常的方便,並且把冗余的代碼動態的裝飾接入點,更加方便了代碼的擴展、可維護性和可讀性,只需要一個代理即可。在這兩種思想之上,spring又構建了一些列的實現方式,通過IoC的Beans組件,以及Context這個讓Beans運行的上下文,以及Core這個Beans的常用工具,使得一切都以Beans為核心,搭建出了最本質的框架,然後由這三者支撐了更加頂層的、復雜的技術,極大地方便了框架的擴展和維護。這也是spring長盛不衰的原因了。

二、spring的框架

2.1、框架的本質

Spring 框架中的核心組件只有三個:Core、Context 和 Beans。它們構建起了整個 Spring 的骨骼架構。沒有它們就不可能有 AOP、Web 等上層的特性功能。如果在它們三個中選出核心的話,那就非 Beans 組件莫屬了,其實 Spring 就是面向 Bean 的編程(BOP,Bean Oriented Programming),Bean 在 Spring 中才是真正的主角。Bean 在 Spring 中作用就像 Object 對 OOP 的意義一樣,沒有對象的概念就像沒有面向對象編程,Spring 中沒有 Bean 也就沒有 Spring 存在的意義。就像一次演出舞臺都準備好了但是卻沒有演員一樣。為什麽要 Bean 這種角色 Bean 或者為何在 Spring 如此重要,這由 Spring 框架的設計目標決定,Spring 為何如此流行,我們用 Spring 的原因是什麽,想想會發現原來 Spring 解決了一個非常關鍵的問題,它可以讓你把對象之間的依賴關系轉而用配置文件來管理,也就是依賴註入機制。而這個註入關系在一個叫 IoC 容器中管理,那 IoC 容器就是被 Bean 包裹的對象。Spring 正是通過把對象包裝在 Bean 中而達到對這些對象的管理以及一些列額外操作的目的。它這種設計策略完全類似於 Java 實現 OOP 的設計理念,當然了 Java 本身的設計要比 Spring 復雜太多太多,但是都是構建一個數據結構,然後根據這個數據結構設計他的生存環境,並讓它在這個環境中按照一定的規律在不停的運動,在它們的不停運動中設計一系列與環境或者與其他個體完成信息交換。這樣想來我們用到的其他框架都是大慨類似的設計理念。

技術分享圖片

2.2、核心組件的協同工作

把 Beans 比作一場演出中的演員的話,那 Context 就是這場演出的舞臺背景,而 Core 應該就是演出的道具了。只有它們在一起才能具備演出一場好戲的最基本條件。當然有最基本的條件還不能使這場演出脫穎而出,還要表演的節目足夠的精彩,這些節目就是 Spring 能提供的特色功能了。我們知道 Bean 包裝的是 Object,而 Object 必然有數據,如何給這些數據提供生存環境就是 Context 要解決的問題,對 Context 來說就是要發現每個 Bean 之間的關系,為它們建立這種關系並且要維護好這種關系。所以 Context 就是一個 Bean 關系的集合,這個關系集合又叫 Ioc 容器,一旦建立起這個 Ioc 容器後 Spring 就可以工作了。那 Core 組件又有什麽用武之地呢?其實 Core 就是發現、建立和維護每個 Bean 之間的關系所需要的一些列的工具,從這個角度看來,Core 這個組件叫 Util 更能理解。

技術分享圖片

2.3、Bean 組件

Bean 組件在 Spring 的 org.springframework.beans 包下。這個包下的所有類主要解決了三件事:Bean 的定義、Bean 的創建以及對 Bean 的解析。對 Spring 的使用者來說唯一需要關心的就是 Bean 的創建,其他兩個由 Spring 在內部幫你完成了,對你來說是透明的。Spring Bean 的創建時典型的工廠模式,它的頂級接口是 BeanFactory,下圖是這個工廠的繼承層次關系:

技術分享圖片

BeanFactory 有三個子類:ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory。但是我們可以發現最終的默認實現類是 DefaultListableBeanFactory,實現了所有的接口。那為何要定義這麽多層次的接口呢?查閱這些接口的源碼和說明發現,每個接口都有使用的場合,它主要是為了區分在 Spring 內部對象的傳遞和轉化過程中,對對象的數據訪問所做的限制。例如 ListableBeanFactory 接口表示這些 Bean 是可列表的,而 HierarchicalBeanFactory 表示的這些 Bean 是有繼承關系的,也就是每個 Bean 有可能有父 Bean。AutowireCapableBeanFactory 接口定義 Bean 的自動裝配規則。這四個接口共同定義了 Bean 的集合、Bean 之間的關系以及 Bean 行為。Bean 的定義主要有 BeanDefinition 描述。

2.4、Context 組件

Context 作為 Spring 的 Ioc 容器,基本上整合了 Spring 的大部分功能,或者說是大部分功能的基礎。Context 在 Spring 的 org.springframework.context 包下, Context 組件實際上就是給 Spring 提供一個運行時的環境,用以保存各個對象的狀態。下面看一下這個環境是如何構建的。ApplicationContext 是 Context 的頂級父類,除了能標識一個應用環境的基本信息外,還繼承了五個接口,這五個接口主要是擴展了 Context 的功能。

技術分享圖片

ApplicationContext 繼承了 BeanFactory,這也說明了 Spring 容器中運行的主體對象是 Beans,另外 ApplicationContext 繼承了 ResourceLoader 接口,使得 ApplicationContext 可以訪問到任何外部資源。
ApplicationContext 的子類主要包含兩個方面:

1  ConfigurableApplicationContext 表示該 Context 是可修改的,也就是在構建 Context 中用戶可以動態添加或修改已有的配置信息,它下面又有多個子類,其中經常使用的是可更新的 Context,即 AbstractRefreshableApplicationContext 類。
2  WebApplicationContext 顧名思義,就是為 web 準備的 Context ,可以直接訪問到 ServletContext,通常情況下,這個接口使用的少。

再往下分就是按照構建 Context 的文件類型,接著就是訪問 Context 的方式。這樣一級一級構成了完整的 Context 等級層次。
總體來說 ApplicationContext 必須要完成以下幾件事:

1   標識一個應用環境
2   利用 BeanFactory 創建 Bean 對象
3   保存對象關系表
4   能夠捕獲各種事件

2.5、Core 組件

Core 組件作為 Spring 的核心組件,其中包含了很多的關鍵類,其中一個重要組成部分就是定義了資源的訪問方式這種把所有資源都抽象成一個接口的方式很值得在以後的設計中拿來學習。下面就重要看一下這個部分在 Spring 的作用。

技術分享圖片
Resource 接口封裝了各種可能的資源類型,也就是對使用者來說屏蔽了文件類型的不同。對資源的提供者來說,如何把資源包裝起來交給其他人用這也是一個問題,我們看到 Resource 接口繼承了 InputStreamSource 接口,這個接口中有個 getInputStream 方法,返回的是 InputStream 類。這樣所有的資源都被可以通過 InputStream 這個類來獲取,所以也屏蔽了資源的提供者。另外還有一個問題就是加載資源的問題,也就是源的加載者要統一,這個任務是由 ResourceLoader 接口完成,它屏蔽了所有的資源加載者的差異,只需要實現這個接口就可以加載所有的資源,默認實現是 DefaultResourceLoader。

Context 和 Resource 建立關系:Context 是把資源的加載、解析和描述工作委托給了 ResourcePatternResolver 類來完成,相當於一個接頭人,把資源的加載、解析和資源的定義整合在一起便於其他組件使用。Core 組件中還有很多類似的方式。

2.6、IoC 容器如何工作

前面介紹了 Core 組件、Bean 組件和 Context 組件的結構與相互關系,下面這裏從使用者角度看一下它們是如何運行的,以及我們如何讓 Spring 完成各種功能。

如何創建 BeanFactory 工廠

IoC容器實際上就是 Context 組件結合其他兩個組件共同構建了一個 Bean 關系網,如何構建這個關系網?構建的入口就在 AbstractApplicationContext 類的 refresh 方法中。這個方法的代碼如下:

 1 public void refresh() throws BeansException, IllegalStateException {
 2     synchronized (this.startupShutdownMonitor) {
 3         // Prepare this context for refreshing.
 4         prepareRefresh();
 5         // Tell the subclass to refresh the internal bean factory.
 6         ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 7         // Prepare the bean factory for use in this context.
 8         prepareBeanFactory(beanFactory);
 9         try {
10             // Allows post-processing of the bean factory in context subclasses.
11             postProcessBeanFactory(beanFactory);
12             // Invoke factory processors registered as beans in the context.
13             invokeBeanFactoryPostProcessors(beanFactory);
14             // Register bean processors that intercept bean creation.
15             registerBeanPostProcessors(beanFactory);
16             // Initialize message source for this context.
17             initMessageSource();
18             // Initialize event multicaster for this context.
19             initApplicationEventMulticaster();
20             // Initialize other special beans in specific context subclasses.
21             onRefresh();
22             // Check for listener beans and register them.
23             registerListeners();
24             // Instantiate all remaining (non-lazy-init) singletons.
25             finishBeanFactoryInitialization(beanFactory);
26             // Last step: publish corresponding event.
27             finishRefresh();
28         }
29         catch (BeansException ex) {
30             // Destroy already created singletons to avoid dangling resources.
31             destroyBeans();
32             // Reset ‘active‘ flag.
33             cancelRefresh(ex);
34             // Propagate exception to caller.
35             throw ex;
36         }
37     }
38 }

這個方法就是構建整個 Ioc 容器過程的完整的代碼,了解裏面的每一行代碼基本上就了解大部分 Spring 的原理和功能了。主要包含這樣幾個步驟:

1  構建 BeanFactory,以便於產生所需的“演員”
2  註冊可能感興趣的事件
3  創建 Bean 實例對象
4  觸發被監聽的事件

創建和配置 BeanFactory,這裏 refresh 也就是刷新配置,前面介紹了 Context 有可更新的子類,這裏正是實現這個功能,當 BeanFactory 已存在是就更新,如果沒有就新創建。下面是更新 BeanFactory 的方法代碼:

 1 protected final void refreshBeanFactory() throws BeansException {
 2     if (hasBeanFactory()) {
 3         destroyBeans();
 4         closeBeanFactory();
 5     }
 6     try {
 7         DefaultListableBeanFactory beanFactory = createBeanFactory();
 8         beanFactory.setSerializationId(getId());
 9         customizeBeanFactory(beanFactory);
10         loadBeanDefinitions(beanFactory);
11         synchronized (this.beanFactoryMonitor) {
12             this.beanFactory = beanFactory;
13         }
14     }
15     catch (IOException ex) {
16         throw new ApplicationContextException(
17             "I/O error parsing bean definition source for "
18             + getDisplayName(), ex);
19     }
20 }

這個方法實現了 AbstractApplicationContext 的抽象方法 refreshBeanFactory,這段代碼清楚的說明了 BeanFactory 的創建過程。註意 BeanFactory 對象的類型的變化,前面介紹了它有很多子類,在什麽情況下使用不同的子類這非常關鍵。BeanFactory 的原始對象是 DefaultListableBeanFactory,設計到後面對這個對象的多種操作。除了 BeanFactory 相關的類外,還發現了與 Bean 的 register 相關。這在 refreshBeanFactory 方法中有一行 loadBeanDefinitions(beanFactory) 將找到答案,這個方法將開始加載、解析 Bean 的定義,也就是把用戶定義的數據結構轉化為 IoC 容器中的特定數據結構。
技術分享圖片

創建 BeanFactory 時序圖:

技術分享圖片

Bean 的解析和登記流程時序圖:
技術分享圖片

創建好 BeanFactory 後,接下去添加一些 Spring 本身需要的一些工具類,這個操作在 AbstractApplicationContext 的 prepareBeanFactory 方法完成。AbstractApplicationContext 中接下來的三行代碼對 Spring 的功能擴展性起了至關重要的作用。前兩行主要是讓我們可以對已經構建的 BeanFactory 的配置做修改,後面一行就是讓我們對以後再創建 Bean 的實例對象時添加一些自定義的操作。所以它們都是擴展了 Spring 的功能,所以我們要學習使用 Spring 必須對這一部分搞清楚。

如何創建 Bean 實例並構建 Bean 的關系網

下面是 Bean 的實例化代碼,是從 finishBeanFactoryInitialization 方法開始的。

 1 protected void finishBeanFactoryInitialization(
 2     ConfigurableListableBeanFactory beanFactory) {
 3  
 4     // Stop using the temporary ClassLoader for type matching.
 5     beanFactory.setTempClassLoader(null);
 6  
 7     // Allow for caching all bean definition metadata, not expecting further changes.
 8     beanFactory.freezeConfiguration();
 9  
10     // Instantiate all remaining (non-lazy-init) singletons.
11     beanFactory.preInstantiateSingletons();
12 }

從上面代碼中可以發現 Bean 的實例化是在 BeanFactory 中發生的。preInstantiateSingletons 方法的代碼如下:

 1 public void preInstantiateSingletons() throws BeansException {
 2     if (this.logger.isInfoEnabled()) {
 3         this.logger.info("Pre-instantiating singletons in " + this);
 4     }
 5     synchronized (this.beanDefinitionMap) {
 6         for (String beanName : this.beanDefinitionNames) {
 7             RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
 8             if (!bd.isAbstract() && bd.isSingleton()
 9                 && !bd.isLazyInit()) {
10                 if (isFactoryBean(beanName)) {
11                     final FactoryBean factory =
12                         (FactoryBean) getBean(FACTORY_BEAN_PREFIX+ beanName);
13                     boolean isEagerInit;
14                     if (System.getSecurityManager() != null
15                         && factory instanceof SmartFactoryBean) {
16                         isEagerInit = AccessController.doPrivileged(
17                             new PrivilegedAction<Boolean>() {
18                             public Boolean run() {
19                                 return ((SmartFactoryBean) factory).isEagerInit();
20                             }
21                         }, getAccessControlContext());
22                     }
23                     else {
24                         isEagerInit = factory instanceof SmartFactoryBean
25                             && ((SmartFactoryBean) factory).isEagerInit();
26                     }
27                     if (isEagerInit) {
28                         getBean(beanName);
29                     }
30                 }
31                 else {
32                     getBean(beanName);
33                 }
34             }
35         }
36     }
37 }

這裏出現了一個非常重要的 Bean—— FactoryBean,可以說 Spring 一大半的擴展的功能都與這個 Bean 有關,這是個特殊的 Bean 是一個工廠 Bean,可以產生 Bean 的 Bean,這裏的產生 Bean 是指 Bean 的實例,如果一個類繼承 FactoryBean 用戶只要實現它的 getObject 方法,就可以自己定義產生實例對象的方法。然而在 Spring 內部這個 Bean 的實例對象是 FactoryBean,通過調用這個對象的 getObject 方法就能獲取用戶自定義產生的對象,從而為 Spring 提供了很好的擴展性。Spring 獲取 FactoryBean 本身的對象是在前面加上 & 來完成的。如何創建 Bean 的實例對象以及如何構建 Bean 實例對象之間的關聯關系式 Spring 中的一個核心關鍵,下面是這個過程的流程圖。

技術分享圖片

如果是普通的 Bean 就直接創建它的實例,是通過調用 getBean 方法。下面是創建 Bean 實例的時序圖:
技術分享圖片

還有一個非常重要的部分就是建立 Bean 對象實例之間的關系,這也是 Spring 框架的核心競爭力,何時、如何建立它們之間的關系:

技術分享圖片

IoC 容器的擴展點

現在還有一個問題就是如何讓這些 Bean 對象有一定的擴展性,就是可以加入用戶的一些操作。那麽有哪些擴展點呢? Spring 又是如何調用到這些擴展點的?對 Spring 的 Ioc 容器來說,主要有這麽幾個。BeanFactoryPostProcessor, BeanPostProcessor。它們分別是在構建 BeanFactory 和構建 Bean 對象時調用。還有就是 InitializingBean 和 DisposableBean,他們分別是在 Bean 實例創建和銷毀時被調用。用戶可以實現這些接口中定義的方法,Spring 就會在適當的時候調用他們。還有一個是 FactoryBean 他是個特殊的 Bean,這個 Bean 可以被用戶更多的控制。這些擴展點通常也是我們使用 Spring 來完成我們特定任務的地方,如何精通 Spring 就看你有沒有掌握好 Spring 有哪些擴展點,並且如何使用他們,要知道如何使用他們就必須了解他們內在的機理。可以用下面一個比喻來解釋。
我們把 Ioc 容器比作一個箱子,這個箱子裏有若幹個球的模子,可以用這些模子來造很多種不同的球,還有一個造這些球模的機器,這個機器可以產生球模。那麽他們的對應關系就是:BeanFactory 是那個造球模的機器,球模就是 Bean,而球模造出來的球就是 Bean 的實例。那前面所說的幾個擴展點又在什麽地方呢? BeanFactoryPostProcessor 對應到當造球模被造出來時,你將有機會可以對其做出適當的修正,也就是他可以幫你修改球模。而 InitializingBean 和 DisposableBean 是在球模造球的開始和結束階段,你可以完成一些預備和掃尾工作。BeanPostProcessor 就可以讓你對球模造出來的球做出適當的修正。最後還有一個 FactoryBean,它可是一個神奇的球模。這個球模不是預先就定型了,而是由你來給他確定它的形狀,既然你可以確定這個球模型的形狀,當然他造出來的球肯定就是你想要的球了,這樣在這個箱子裏你可以發現所有你想要的球。

Ioc 容器如何使用

我們使用 Spring 必須要首先構建 Ioc 容器,沒有它 Spring 無法工作,ApplicatonContext.xml 就是 IoC 容器的默認配置文件,Spring 的所有特性功能都是基於這個 Ioc 容器工作的,比如AOP。IoC 它實際上就是為你構建了一個魔方,Spring 為你搭好了骨骼架構,這個魔方到底能變出什麽好的東西出來,這必須要有你的參與。那我們怎麽參與?這就是前面說的要了解 Spring 中有哪些擴展點,我們通過實現那些擴展點來改變 Spring 的通用行為。至於如何實現擴展點來得到我們想要的個性結果,Spring 中有很多例子,其中 AOP 的實現就是 Spring 本身實現了其擴展點來達到了它想要的特性功能。

技術分享圖片

通過從 Spring 的幾個核心組件入手,試圖找出構建 Spring 框架的骨骼架構,進而分析 Spring 在設計時的一些設計理念,是否從中找出一些好的設計思想,對我們以後程序設計能提供一些思路。接著再詳細分析了 Spring 中是如何實現這些理念的,以及在設計模式上是如何使用的。通過分析 Spring 給我一個很大的啟示就是這套設計理念其實對我們有很強的借鑒意義,它通過抽象復雜多變的對象,進一步做規範,然後根據它定義的這套規範設計出一個容器,容器中構建它們的復雜關系,其實現在有很多情況都可以用這種類似的處理方法。

三、總結

在spring中,我們知道了其中的本質——‘道’,然後在‘術’上進行實現,並且發現spring中提供了很多的擴展,從此對spring有了更深的理解,同時隨著版本的遞增,其中的各種功能也進行了很多的優化和提升,我們最好多閱讀一下spring的源代碼,從中明白很多程序背後的機理。

參考文獻:https://www.ibm.com/developerworks/cn/java/j-lo-spring-principle/

沈澱再出發:spring的架構理解