Spring框架系列(6) - Spring IOC實現原理詳解之IOC體系結構設計

pdai發表於2022-06-27

在對IoC有了初步的認知後,我們開始對IOC的實現原理進行深入理解。本文將幫助你站在設計者的角度去看IOC最頂層的結構設計。@pdai

站在設計者的角度考慮設計IOC容器

如果讓你來設計一個IoC容器,你會怎麼設計?我們初步的通過這個問題,來幫助我們更好的理解IOC的設計。

在設計時,首先需要考慮的是IOC容器的功能(輸入和輸出), 承接前面的文章,我們初步的畫出IOC容器的整體功能。

在此基礎上,我們初步的去思考,如果作為一個IOC容器的設計者,主體上應該包含哪幾個部分:

  • 載入Bean的配置(比如xml配置)
    • 比如不同型別資源的載入,解析成生成統一Bean的定義
  • 根據Bean的定義載入生成Bean的例項,並放置在Bean容器中
    • 比如Bean的依賴注入,Bean的巢狀,Bean存放(快取)等
  • 除了基礎Bean外,還有常規針對企業級業務的特別Bean
    • 比如國際化Message,事件Event等生成特殊的類結構去支撐
  • 對容器中的Bean提供統一的管理和呼叫
    • 比如用工廠模式管理,提供方法根據名字/類的型別等從容器中獲取Bean
  • ...

(pdai:這種思考的過程才是建設性的,知識體系的構建才是高效的)

Spring IoC的體系結構設計

那麼我們來看下Spring設計者是如何設計IoC並解決這些問題的。

BeanFactory和BeanRegistry:IOC容器功能規範和Bean的註冊

Spring Bean的建立是典型的工廠模式,這一系列的Bean工廠,也即IOC容器為開發者管理物件間的依賴關係提供了很多便利和基礎服務,在Spring中有許多的IOC容器的實現供使用者選擇和使用,這是IOC容器的基礎;在頂層的結構設計主要圍繞著BeanFactory和xxxRegistry進行:

  • BeanFactory: 工廠模式定義了IOC容器的基本功能規範
  • BeanRegistry: 向IOC容器手工註冊 BeanDefinition 物件的方法

其相互關係如下:

我們再通過幾個問題來輔助理解。

BeanFactory定義了IOC 容器基本功能規範?

BeanFactory作為最頂層的一個介面類,它定義了IOC容器的基本功能規範,BeanFactory 有三個子類:ListableBeanFactory、HierarchicalBeanFactory 和AutowireCapableBeanFactory。我們看下BeanFactory介面:

public interface BeanFactory {    
      
    //用於取消引用例項並將其與FactoryBean建立的bean區分開來。例如,如果命名的bean是FactoryBean,則獲取將返回Factory,而不是Factory返回的例項。
    String FACTORY_BEAN_PREFIX = "&"; 
        
    //根據bean的名字和Class型別等來得到bean例項    
    Object getBean(String name) throws BeansException;    
    Object getBean(String name, Class requiredType) throws BeansException;    
    Object getBean(String name, Object... args) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

    //返回指定bean的Provider
    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

    //檢查工廠中是否包含給定name的bean,或者外部註冊的bean
    boolean containsBean(String name);

    //檢查所給定name的bean是否為單例/原型
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    //判斷所給name的型別與type是否匹配
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    //獲取給定name的bean的型別
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    //返回給定name的bean的別名
    String[] getAliases(String name);
     
}

BeanFactory為何要定義這麼多層次的介面?定義了哪些介面?

主要是為了區分在 Spring 內部在操作過程中物件的傳遞和轉化過程中,對物件的資料訪問所做的限制

有哪些介面呢?

  • ListableBeanFactory:該介面定義了訪問容器中 Bean 基本資訊的若干方法,如檢視Bean 的個數、獲取某一型別 Bean 的配置名、檢視容器中是否包括某一 Bean 等方法;
  • HierarchicalBeanFactory:父子級聯 IoC 容器的介面,子容器可以通過介面方法訪問父容器; 通過 HierarchicalBeanFactory 介面, Spring 的 IoC 容器可以建立父子層級關聯的容器體系,子容器可以訪問父容器中的 Bean,但父容器不能訪問子容器的 Bean。Spring 使用父子容器實現了很多功能,比如在 Spring MVC 中,展現層 Bean 位於一個子容器中,而業務層和持久層的 Bean 位於父容器中。這樣,展現層 Bean 就可以引用業務層和持久層的 Bean,而業務層和持久層的 Bean 則看不到展現層的 Bean。
  • ConfigurableBeanFactory:是一個重要的介面,增強了 IoC 容器的可定製性,它定義了設定類裝載器、屬性編輯器、容器初始化後置處理器等方法;
  • ConfigurableListableBeanFactory: ListableBeanFactory 和 ConfigurableBeanFactory的融合;
  • AutowireCapableBeanFactory:定義了將容器中的 Bean 按某種規則(如按名字匹配、按型別匹配等)進行自動裝配的方法;

如何將Bean註冊到BeanFactory中?BeanRegistry

Spring 配置檔案中每一個<bean>節點元素在 Spring 容器裡都通過一個 BeanDefinition 物件表示,它描述了 Bean 的配置資訊。而 BeanDefinitionRegistry 介面提供了向容器手工註冊 BeanDefinition 物件的方法。

BeanDefinition:各種Bean物件及其相互的關係

Bean物件存在依賴巢狀等關係,所以設計者設計了BeanDefinition,它用來對Bean物件及關係定義;我們在理解時只需要抓住如下三個要點:

  • BeanDefinition 定義了各種Bean物件及其相互的關係
  • BeanDefinitionReader 這是BeanDefinition的解析器
  • BeanDefinitionHolder 這是BeanDefination的包裝類,用來儲存BeanDefinition,name以及aliases等。
  • BeanDefinition

SpringIOC容器管理了我們定義的各種Bean物件及其相互的關係,Bean物件在Spring實現中是以BeanDefinition來描述的,其繼承體系如下

  • BeanDefinitionReader

Bean 的解析過程非常複雜,功能被分的很細,因為這裡需要被擴充套件的地方很多,必須保證有足夠的靈活性,以應對可能的變化。Bean 的解析主要就是對 Spring 配置檔案的解析。這個解析過程主要通過下圖中的類完成:

  • BeanDefinitionHolder

BeanDefinitionHolder 這是BeanDefination的包裝類,用來儲存BeanDefinition,name以及aliases等

ApplicationContext:IOC介面設計和實現

IoC容器的介面類是ApplicationContext,很顯然它必然繼承BeanFactory對Bean規範(最基本的ioc容器的實現)進行定義。而ApplicationContext表示的是應用的上下文,除了對Bean的管理外,還至少應該包含了

  • 訪問資源: 對不同方式的Bean配置(即資源)進行載入。(實現ResourcePatternResolver介面)
  • 國際化: 支援資訊源,可以實現國際化。(實現MessageSource介面)
  • 應用事件: 支援應用事件。(實現ApplicationEventPublisher介面)

ApplicationContext介面的設計

我們來看下ApplicationContext整體結構

  • HierarchicalBeanFactory 和 ListableBeanFactory: ApplicationContext 繼承了 HierarchicalBeanFactory 和 ListableBeanFactory 介面,在此基礎上,還通過多個其他的介面擴充套件了 BeanFactory 的功能:
  • ApplicationEventPublisher:讓容器擁有釋出應用上下文事件的功能,包括容器啟動事件、關閉事件等。實現了 ApplicationListener 事件監聽介面的 Bean 可以接收到容器事件 , 並對事件進行響應處理 。 在 ApplicationContext 抽象實現類AbstractApplicationContext 中,我們可以發現存在一個 ApplicationEventMulticaster,它負責儲存所有監聽器,以便在容器產生上下文事件時通知這些事件監聽者。
  • MessageSource:為應用提供 i18n 國際化訊息訪問的功能;
  • ResourcePatternResolver : 所 有 ApplicationContext 實現類都實現了類似於PathMatchingResourcePatternResolver 的功能,可以通過帶字首的 Ant 風格的資原始檔路徑裝載 Spring 的配置檔案。
  • LifeCycle:該介面是 Spring 2.0 加入的,該介面提供了 start()和 stop()兩個方法,主要用於控制非同步處理過程。在具體使用時,該介面同時被 ApplicationContext 實現及具體 Bean 實現, ApplicationContext 會將 start/stop 的資訊傳遞給容器中所有實現了該介面的 Bean,以達到管理和控制 JMX、任務排程等目的。

ApplicationContext介面的實現

在考慮ApplicationContext介面的實現時,關鍵的點在於,不同Bean的配置方式(比如xml,groovy,annotation等)有著不同的資源載入方式,這便衍生除了眾多ApplicationContext的實現類。

第一,從類結構設計上看, 圍繞著是否需要Refresh容器衍生出兩個抽象類

  • GenericApplicationContext: 是初始化的時候就建立容器,往後的每次refresh都不會更改

  • AbstractRefreshableApplicationContext: AbstractRefreshableApplicationContext及子類的每次refresh都是先清除已有(如果不存在就建立)的容器,然後再重新建立;AbstractRefreshableApplicationContext及子類無法做到GenericApplicationContext混合搭配從不同源頭獲取bean的定義資訊

第二, 從載入的源來看(比如xml,groovy,annotation等), 衍生出眾多型別的ApplicationContext, 典型比如:

  • FileSystemXmlApplicationContext: 從檔案系統下的一個或多個xml配置檔案中載入上下文定義,也就是說系統碟符中載入xml配置檔案。
  • ClassPathXmlApplicationContext: 從類路徑下的一個或多個xml配置檔案中載入上下文定義,適用於xml配置的方式。
  • AnnotationConfigApplicationContext: 從一個或多個基於java的配置類中載入上下文定義,適用於java註解的方式。
  • ConfigurableApplicationContext: 擴充套件於 ApplicationContext,它新增加了兩個主要的方法: refresh()和 close(),讓 ApplicationContext 具有啟動、重新整理和關閉應用上下文的能力。在應用上下文關閉的情況下呼叫 refresh()即可啟動應用上下文,在已經啟動的狀態下,呼叫 refresh()則清除快取並重新裝載配置資訊,而呼叫close()則可關閉應用上下文。這些介面方法為容器的控制管理帶來了便利,但作為開發者,我們並不需要過多關心這些方法。

第三, 更進一步理解

設計者在設計時AnnotationConfigApplicationContext為什麼是繼承GenericApplicationContext? 因為基於註解的配置,是不太會被執行時修改的,這意味著不需要進行動態Bean配置和重新整理容器,所以只需要GenericApplicationContext。

而基於XML這種配置檔案,這種檔案是容易修改的,需要動態性重新整理Bean的支援,所以XML相關的配置必然繼承AbstractRefreshableApplicationContext; 且存在多種xml的載入方式(位置不同的設計),所以必然會設計出AbstractXmlApplicationContext, 其中包含對XML配置解析成BeanDefination的過程。

那麼細心的你從上圖可以發現AnnotationWebConfigApplicationContext卻是繼承了AbstractRefreshableApplicationContext而不是GenericApplicationContext, 為什麼AnnotationWebConfigApplicationContext繼承自AbstractRefreshableApplicationContext呢 ? 因為使用者可以通過ApplicationContextInitializer來設定contextInitializerClasses(context-param / init-param), 在這種情況下使用者傾向於重新整理Bean的,所以設計者選擇讓AnnotationWebConfigApplicationContext繼承了AbstractRefreshableApplicationContext。(如下是原始碼中Spring設計者對它的解釋)

 * <p>As an alternative to setting the "contextConfigLocation" parameter, users may
 * implement an {@link org.springframework.context.ApplicationContextInitializer
 * ApplicationContextInitializer} and set the
 * {@linkplain ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM "contextInitializerClasses"}
 * context-param / init-param. In such cases, users should favor the {@link #refresh()}
 * and {@link #scan(String...)} methods over the {@link #setConfigLocation(String)}
 * method, which is primarily for use by {@code ContextLoader}.

我們把之前的設計要點和設計結構結合起來看:

到此,基本可以幫助你從頂層構建對IoC容器的設計理解,而不是過早沉溺於程式碼的細節; 所以《Java全棧知識體系》最大的目標是幫助你構築體系化的認知,如果你自己去看原始碼而不站在頂層設計角度出發, 你多半會撿了芝麻丟了西瓜,時間一長啥印象沒有。@pdai

參考文章

https://www.cnblogs.com/ITtangtang/p/3978349.html

更多文章

首先, 從Spring框架的整體架構和組成對整體框架有個認知。

  • Spring基礎 - Spring和Spring框架組成
    • Spring是什麼?它是怎麼誕生的?有哪些主要的元件和核心功能呢? 本文通過這幾個問題幫助你構築Spring和Spring Framework的整體認知。

其次,通過案例引出Spring的核心(IoC和AOP),同時對IoC和AOP進行案例使用分析。

基於Spring框架和IOC,AOP的基礎,為構建上層web應用,需要進一步學習SpringMVC。

  • Spring基礎 - SpringMVC請求流程和案例
    • 前文我們介紹了Spring框架和Spring框架中最為重要的兩個技術點(IOC和AOP),那我們如何更好的構建上層的應用呢(比如web 應用),這便是SpringMVC;Spring MVC是Spring在Spring Container Core和AOP等技術基礎上,遵循上述Web MVC的規範推出的web開發框架,目的是為了簡化Java棧的web開發。 本文主要介紹SpringMVC的請求流程和基礎案例的編寫和執行。

Spring進階 - IoC,AOP以及SpringMVC的原始碼分析

  • Spring進階 - Spring IOC實現原理詳解之IOC體系結構設計
    • 在對IoC有了初步的認知後,我們開始對IOC的實現原理進行深入理解。本文將幫助你站在設計者的角度去看IOC最頂層的結構設計
  • Spring進階 - Spring IOC實現原理詳解之IOC初始化流程
    • 上文,我們看了IOC設計要點和設計結構;緊接著這篇,我們可以看下原始碼的實現了:Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的
  • Spring進階 - Spring IOC實現原理詳解之Bean例項化(生命週期,迴圈依賴等)
    • 上文,我們看了IOC設計要點和設計結構;以及Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的;容器中存放的是Bean的定義即BeanDefinition放到beanDefinitionMap中,本質上是一個ConcurrentHashMap<String, Object>;並且BeanDefinition介面中包含了這個類的Class資訊以及是否是單例等。那麼如何從BeanDefinition中例項化Bean物件呢,這是本文主要研究的內容?
  • Spring進階 - Spring AOP實現原理詳解之切面實現
    • 前文,我們分析了Spring IOC的初始化過程和Bean的生命週期等,而Spring AOP也是基於IOC的Bean載入來實現的。本文主要介紹Spring AOP原理解析的切面實現過程(將切面類的所有切面方法根據使用的註解生成對應Advice,並將Advice連同切入點匹配器和切面類等資訊一併封裝到Advisor,為後續交給代理增強實現做準備的過程)。
  • Spring進階 - Spring AOP實現原理詳解之AOP代理
    • 上文我們介紹了Spring AOP原理解析的切面實現過程(將切面類的所有切面方法根據使用的註解生成對應Advice,並將Advice連同切入點匹配器和切面類等資訊一併封裝到Advisor)。本文在此基礎上繼續介紹,代理(cglib代理和JDK代理)的實現過程。
  • Spring進階 - Spring AOP實現原理詳解之Cglib代理實現
    • 我們在前文中已經介紹了SpringAOP的切面實現和建立動態代理的過程,那麼動態代理是如何工作的呢?本文主要介紹Cglib動態代理的案例和SpringAOP實現的原理。
  • Spring進階 - Spring AOP實現原理詳解之JDK代理實現
    • 上文我們學習了SpringAOP Cglib動態代理的實現,本文主要是SpringAOP JDK動態代理的案例和實現部分。
  • Spring進階 - SpringMVC實現原理之DispatcherServlet初始化的過程
    • 前文我們有了IOC的原始碼基礎以及SpringMVC的基礎,我們便可以進一步深入理解SpringMVC主要實現原理,包含DispatcherServlet的初始化過程和DispatcherServlet處理請求的過程的原始碼解析。本文是第一篇:DispatcherServlet的初始化過程的原始碼解析。
  • Spring進階 - SpringMVC實現原理之DispatcherServlet處理請求的過程
    • 前文我們有了IOC的原始碼基礎以及SpringMVC的基礎,我們便可以進一步深入理解SpringMVC主要實現原理,包含DispatcherServlet的初始化過程和DispatcherServlet處理請求的過程的原始碼解析。本文是第二篇:DispatcherServlet處理請求的過程的原始碼解析。

相關文章