Spring源碼閱讀-ApplicationContext體繫結構分析

来源:https://www.cnblogs.com/zhangfengxian/archive/2019/07/15/11192054.html
-Advertisement-
Play Games

[TOC] 上篇已經對IoC容器的設計進行了分析( "Spring源碼閱讀 IoC容器解析" ),本篇將對 經典的繼承層次圖進行詳細的分析,在心中形成一個大致的印象,以便後面一步步調試源碼的時候,不會太眼花繚亂。讓我們一步步的前進吧... 繼承層次圖概覽 使用IDEA的繼承層次工具生成如下的圖(選中 ...


目錄

上篇已經對IoC容器的設計進行了分析(Spring源碼閱讀-IoC容器解析),本篇將對ApplicationContext經典的繼承層次圖進行詳細的分析,在心中形成一個大致的印象,以便後面一步步調試源碼的時候,不會太眼花繚亂。讓我們一步步的前進吧...

繼承層次圖概覽

使用IDEA的繼承層次工具生成如下的圖(選中ApplicationContext --> Ctrl+H):

(溫馨提示:雙擊可查看高清大圖)

從上圖能很清楚的看出,ApplicationContext的子介面分為兩個部分:

  • ConfigurableApplicationContext:大部分的應用上下文都實現了該介面
  • WebApplicationContext:在web應用程式中使用

ConfigurableApplicationContext分析

從上面的類的繼承層次圖能看到,ConfigurableApplicationContext是比較上層的一個介面,該介面也是比較重要的一個介面,幾乎所有的應用上下文都實現了該介面。該介面在ApplicationContext的基礎上提供了配置應用上下文的能力,此外提供了生命周期的控制能力。先看一下該介面的繼承關係圖(為了更加簡潔,去掉了ApplicationContext繼承的介面):

(溫馨提示:雙擊可查看高清大圖)

Closeable介面用於關閉應用上下文,釋放所有的資源和鎖,這也包括摧毀所有緩存的單例的bean,常見的try-with-resources用法如下,執行完try體中的代碼後會自動的調用close方法:

try (ConfigurableApplicationContext cac = ...) {
    // 編寫代碼 
    ...
}

Lifecycle定義了啟動/停止生命周期的控制的一些方法,其中的方法如下:

void start(); // 啟動組件
void stop(); // 停止組件
boolean isRunning(); // 組件是否正在運行

接下來看一下ConfigurableApplicationContext中的方法:

void setId(String id); // 設置應用上下文唯一的id
void setParent(ApplicationContext parent); // 設置應用程式上下文的父級
void setEnvironment(ConfigurableEnvironment environment); // 設置應用上下文的環境
ConfigurableEnvironment getEnvironment();  // 獲取應用上下文的環境
// 添加一個新的BeanFactoryPostProcessor
void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);
// 添加應用程式監聽器
void addApplicationListener(ApplicationListener<?> listener);
// 添加協議解析器,可能會覆蓋預設的規則
void addProtocolResolver(ProtocolResolver resolver);
// 載入或者刷新配置
void refresh() throws BeansException, IllegalStateException;
// 向JVM runtime註冊一個關閉鉤子,JVM關閉時關閉這個上下文
void registerShutdownHook();
// 應用程式上問下是否是激活狀態
boolean isActive();
// 獲取應用上下文內部的bean factory
ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

上面的這些方法基本上是提供了對某些特性的實現進行支撐的方法。

看了這麼多方法,下麵看一下ApplicationContext的抽象的實現。

AbstractApplicationContext

AbstractApplicationContextApplicationContext介面的抽象實現,這個抽象類僅僅是實現了公共的上下文特性。這個抽象類使用了模板方法設計模式,需要具體的實現類去實現這些抽象的方法。對相關介面的實現如下:

  • ApplicationContext介面的實現
  • ConfigurableApplicationContext介面的實現
  • BeanFactory介面的實現
  • ListableBeanFactory介面的實現
  • HierarchicalBeanFactory介面的實現
  • MessageSource介面的實現
  • ResourcePatternResolver的實現
  • Lifecycle介面的實現

本文不會詳細的講解這個類中的具體的實現細節,後面會有更加的詳細的介紹。下麵看下裡面的抽象方法:

// 刷新BeanFactory,用於執行實際的配置載入,該方法在其他的初始化工作之前被refresh()方法調用
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
// 關閉BeanFactory,用於釋放內部使用的BeanFactory·
protected abstract void closeBeanFactory();
// 獲取內部使用的BeanFactory
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

那麼對需要實現的方法經過抽象後,只剩下少量的需要子類去實現的方法。

GenericApplicationContext

GenericApplicationContext繼承自AbstractApplicationContext,是為通用目的設計的,它能載入各種配置文件,例如xml,properties等等。它的內部持有一個DefaultListableBeanFactory的實例,實現了BeanDefinitionRegistry介面,以便允許向其應用任何bean的定義的讀取器。為了能夠註冊bean的定義,refresh()只允許調用一次。常見的使用如下:

GenericApplicationContext ctx = new GenericApplicationContext();
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
xmlReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(ctx);
propReader.loadBeanDefinitions(new ClassPathResource("otherBeans.properties"));
ctx.refresh();

MyBean myBean = (MyBean) ctx.getBean("myBean");
..

這個類的實現沒有太多需要註意的地方,需要註意的有兩點:

  • 內部使用的DefaultListableBeanFactory的實例,提供了一些方法來配置該實例,例如是否允許bean定義的覆蓋、是否允許bean之間的迴圈應用等等。
  • 該類實現了BeanDefinitionRegistry,bean的定義註冊。以便能通過BeanDefinitionReader讀取bean的配置,並註冊。BeanDefinitionRegistry介面的實現是直接使用內部的DefaultListableBeanFactory的實例。

GenericXmlApplicationContext

GenericXmlApplicationContext繼承自GenericApplicationContext,內置了對XML的支持。它非常的方便和靈活,是ClassPathXmlApplicationContextFileSystemXmlApplicationContext的一種替代品。可以發現,它的內部有一個XmlBeanDefinitionReader的實例,專門用於處理XML的配置。

StaticApplicationContext

StaticApplicationContext繼承自GenericApplicationContext,主要用於編程式的註入bean和消息,而不是從外部的配置源讀取bean的定義。主要是在測試時非常有用。通過閱讀源代碼可以看到,它的內部有一個StaticMessageSource的實例,使用addMessage方法添加消息。每次在編程式的註入bean時,都會創建一個GenericBeanDefinition的實例。

ResourceAdapterApplicationContext

ResourceAdapterApplicationContext繼承自GenericApplicationContext,是為JCA(J2EE Connector Architecture)的ResourceAdapter設計的,主要用於傳遞BootstrapContext的實例給實現了BootstrapContextAware介面且由spring管理的bean。覆蓋了postProcessBeanFactory方法來實現此功能。

GenericGroovyApplicationContext

GenericGroovyApplicationContext繼承自GenericApplicationContext,實現了GroovyObject介面以便能夠使用點的語法(.xx)取代getBean方法來獲取bean。它主要用於Groovy bean的定義,與GenericXmlApplicationContext一樣,它也能解析XML格式定義的bean。內部使用GroovyBeanDefinitionReader來完成groovy腳本和XML的解析。

AnnotationConfigApplicationContext

AnnotationConfigApplicationContext繼承自GenericApplicationContext,提供了註解配置(例如:Configuration、Component、inject等)和類路徑掃描(scan方法)的支持,可以使用register(Class<?>... annotatedClasses)來註冊一個一個的進行註冊。實現了AnnotationConfigRegistry介面,來完成對註冊配置的支持,只有兩個方法:register和scan。內部使用AnnotatedBeanDefinitionReader來完成註解配置的解析,使用ClassPathBeanDefinitionScanner來完成類路徑下的bean定義的掃描。

AbstractRefreshableApplicationContext

AbstractRefreshableApplicationContext繼承自AbstractApplicationContext,支持多次進行刷新(多次調用refresh方法),每次刷新時在內部創建一個新的bean工廠的實例。子類僅僅需要實現loadBeanDefinitions方法,該方法在每次刷新時都會調用。

AbstractRefreshableConfigApplicationContext

AbstractRefreshableConfigApplicationContext繼承自AbstractRefreshableApplicationContext,添加了對指定的配置文件路徑的公共的處理,可以把他看作基於XML的應用上下文的基類。實現瞭如下的兩個介面:

  • BeanNameAware用於設置上下文的bean的名稱,只有一個方法:void setBeanName(String name)
  • InitializingBean用於上下文一切就緒後,如果還未刷新,那麼就執行刷新操作,只有一個方法:void afterPropertiesSet()

AbstractXmlApplicationContext

AbstractXmlApplicationContext繼承自AbstractRefreshableConfigApplicationContext,用於描繪包含能被XmlBeanDefinitionReader所理解的bean定義的XML文檔。子類只需要實現getConfigResourcesgetConfigLocations來提供配置文件資源。

FileSystemXmlApplicationContext

FileSystemXmlApplicationContext繼承自AbstractXmlApplicationContext,用於解析文件系統中XML配置文件。文件的路徑可以是具體的文件路徑,例如:xxx/application.xml,也可以是ant風格的配置,例如:xxx/*-context.xml。

看下麵的通過文件路徑來獲取資源的代碼:

@Override
protected Resource getResourceByPath(String path) {
    if (path.startsWith("/")) {
        path = path.substring(1);
    }
    return new FileSystemResource(path);
}

文件路徑前面的/會被去掉,無論是否路徑前面是否加上/,文件路徑都會解析成相對路徑,即基於JVM的當前工作路徑。獲取到的資源對象是FileSystemResource,用於處理文件系統相關的資源。

ClassPathXmlApplicationContext

ClassPathXmlApplicationContext繼承自AbstractXmlApplicationContext,和FileSystemXmlApplicationContext類似,只不過ClassPathXmlApplicationContext是用於處理類路徑下的XML配置文件。文件的路徑可以是具體的文件路徑,例如:xxx/application.xml,也可以是ant風格的配置,例如:xxx/*-context.xml。

WebApplicationContext

該介面提供了在web應用中的配置,介面提供了一個ServletContext getServletContext()用來獲取ServletContext對象。該介面會和ServletContext的一個屬性進行綁定,這個屬性就是ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。定義了三個作用域的名稱:SCOPE_REQUEST,SCOPE_SESSION,SCOPE_APPLICATION。在工廠中的bean的名稱:SERVLET_CONTEXT_BEAN_NAME。ServletContext初始化參數名稱:CONTEXT_PARAMETERS_BEAN_NAME。在工廠中ServletContext屬性值環境bean的名稱:CONTEXT_ATTRIBUTES_BEAN_NAME

ConfigurableWebApplicationContext

ConfigurableWebApplicationContext繼承自WebApplicationContextConfigurableApplicationContext,提供了web應用上下文的可配置的能力。相關介面定義如下:

// 設置web應用上下文的ServletContext
void setServletContext(@Nullable ServletContext servletContext);
// 設置web應用上下文的ServletConfig
void setServletConfig(@Nullable ServletConfig servletConfig);
// 獲取web應用上下文的ServletConfig
ServletConfig getServletConfig();
// 設置web應用上下文的命名空間
void setNamespace(@Nullable String namespace);
// 獲取web應用上下文的命名空間
String getNamespace();
// 以初始化參數的形式設置web應用上下文的配置文件位置
void setConfigLocation(String configLocation);
// 設置web應用上下文的配置文件的位置
void setConfigLocations(String... configLocations);
// 獲取web應用上下文的配置文件位置
String[] getConfigLocations();

上面的介面主要都是一些設置或者獲取的方法,在web應用上下文中需要用到的一些東西。

GenericWebApplicationContext

GenericWebApplicationContext繼承自GenericApplicationContext,實現了ConfigurableWebApplicationContextThemeSource介面。該類設計的目的不是在web.xml中進行聲明式的安裝,而是編程式的安裝,例如使用WebApplicationInitializers來構建內嵌的上下文。該介面在ConfigurableWebApplicationContext的內容都是一個偽實現,調用其中的大多數方法都會拋出異常。你也許註意到了,他實現了ThemeSource介面,那麼他有什麼用呢?字面意思是主題源,它設計的目的主要是用於消息的國際化。

StaticWebApplicationContext

StaticWebApplicationContext繼承自StaticApplicationContext實現了ConfigurableWebApplicationContextThemeSource介面。該介面主要是用在測試的環境,不用於產品環境。

AbstractRefreshableWebApplicationContext

GenericWebApplicationContext繼承自AbstractRefreshableConfigApplicationContext,實現了ConfigurableWebApplicationContextThemeSource介面,主要是用於web環境下。在web程式啟動的時候,提供一個configLocations屬性,通過ConfigurableWebApplicationContext介面來進行填充。子類化這個介面是很簡單的,所有你所需要做的事情就是實現loadBeanDefinitions方法,來實現你自己的bean定義的載入邏輯。

XmlWebApplicationContext

XmlWebApplicationContext繼承自AbstractRefreshableWebApplicationContext,接受能被XmlBeanDefinitionReader所理解的XML文檔配置。對於根上下文,預設的配置文件路徑是/WEB-INF/applicationContext.xml,對於命名空間為test-servlet的上下文,預設的配置文件路徑是/WEB-INF/test-servlet.xml(就像servlet-name為test的DispatcherServlet實例)。

預設的配置文件路徑處理的代碼如下:

protected String[] getDefaultConfigLocations() {
    if (getNamespace() != null) {
        return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
    }
    else {
        return new String[] {DEFAULT_CONFIG_LOCATION};
    }
}

和其他的上下文一樣,bean定義的載入也是在void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法中,使用的是XmlBeanDefinitionReader

GroovyWebApplicationContext

GroovyWebApplicationContext繼承自AbstractRefreshableWebApplicationContext,實現了GroovyObject介面,接受能被GroovyBeanDefinitionReader所理解的groovy bean定義腳本和XML文檔配置。對於web環境,基本上是和GenericGroovyApplicationContext是等價的。對於根上下文,預設的配置文件路徑是/WEB-INF/applicationContext.groovy,對於命名空間為test-servlet的上下文,預設的配置文件路徑是/WEB-INF/test-servlet.xml(就像servlet-name為test的DispatcherServlet實例)。

預設的配置文件路徑處理的代碼如下:

protected String[] getDefaultConfigLocations() {
    if (getNamespace() != null) {
        return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
    }
    else {
        return new String[] {DEFAULT_CONFIG_LOCATION};
    }
}

和其他的上下文一樣,bean定義的載入也是在void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法中,使用的是GroovyBeanDefinitionReader

AnnotationConfigWebApplicationContext

AnnotationConfigWebApplicationContext繼承自AbstractRefreshableWebApplicationContext

接受註解的類作為輸入(特殊的@Configuration註解類,一般的@Component註解類,與JSR-330相容的javax.inject註解)。允許一個一個的註入,同樣也能使用類路徑掃描。對於web環境,基本上是和AnnotationConfigApplicationContext等價的。使用AnnotatedBeanDefinitionReader來對註解的bean進行處理,使用ClassPathBeanDefinitionScanner來對類路徑下的bean進行掃描。

部分代碼如下:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
    AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
    ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory);
    ...
    if (!this.annotatedClasses.isEmpty()) {
       ....
        reader.register(ClassUtils.toClassArray(this.annotatedClasses));
    }

    if (!this.basePackages.isEmpty()) {
        ....
        scanner.scan(StringUtils.toStringArray(this.basePackages));
    }

    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            try {
                Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader());
                if (logger.isTraceEnabled()) {
                    logger.trace("Registering [" + configLocation + "]");
                }
                reader.register(clazz);
            }
            catch (ClassNotFoundException ex) {
                ....
                }
            }
        }
    }
}

本文思維導圖


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在目前的工作中,我對Java中的Stream和Lambda表達式都使用得很多,之前也寫了兩篇文章來總結對應的知識。 "024:Java流實現Shell:cat 1.log | grep a | sort | uniq c | sort rn" "函數式編程讓你忘記設計模式" 不過對於Optional ...
  • 這裡先簡單說下最大堆的基本性質: 最大堆一定是完全二叉樹 當父節點為 n 時,左孩子為 n 2 + 1,右孩子為 n 2 + 2 當孩子為 n 時,其父節點為: (n 1) / 2 這一點很重要,在後面初始化的時候會用到 父節點大於等於左孩子和右孩子,但左孩子不一定大於右孩子 瞭解以上基本性質之後, ...
  • 一、文件 1.定義:長久保存信息的一種信息集合 2.常用操作:(1)打開關閉(2)讀寫內容(3)查找 3.open函數 (1)意義:打開文件,帶有很多參數 (2)第一個參數:必須有,文件的路徑和名稱 mode:表明文件用什麼方式打開 i.r代表只讀的方式打開;ii.w:寫方式打開,會覆蓋以前的內容; ...
  • Mybatis映射文件的SQL深入 (Mybatis今天學的不多,看了半天的mysql必知必會) 動態sql語句 if語句,查詢一個用戶的時候,有可能根據地址查詢,用戶名查詢,性別查詢等,所以需要動態sql語句 1.介面中新添加方法 2.配置文件的寫法,根據條件查詢 3.測試方法 mybatis中的 ...
  • "Add Two Numbers" Example: Code // // main.cpp // 兩個數字的加法操作 // // Created by mac on 2019/7/14. // Copyright © 2019 mac. All rights reserved. // includ ...
  • C++編譯過程 C++ 編譯過程在介紹編譯器之前,先簡單地說一下 C++ 的編譯過程,以便理解編譯器的工作。編譯(compiling)並不意味著只創建僅僅一個可執行文件。創建一個可執行文件是一個多級過程,其中最重要的過程是預處理(preprocessing),編譯(compliation)和鏈接(l ...
  • 一、基本簡介 1、基礎概念 在矩陣中,若數值為0的元素數目遠遠多於非0元素的數目,並且非0元素分佈沒有規律時,則稱該矩陣為稀疏矩陣;與之相反,若非0元素數目占大多數時,則稱該矩陣為稠密矩陣。定義非零元素的總數比上矩陣所有元素的總數為矩陣的稠密度。 2、處理方式 3、圖解描述 4、五子棋場景 二、代碼 ...
  • 1.str.capitalize() 將原字元串內的首字母轉成大寫,其他部分小寫,再返回新字元串 Output: 2.str.upper() 將原字元串的字母轉為大寫 Output: 3.str.lower() 將原字元串的字母轉為小寫 Output: 4.str.swapcase() 將原字元串內 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...