Spring(二)核心容器 - 簡介 、BeanFactory、ApplicationContext

前言

在上篇文章中,和你們一塊兒討論了 Spring 的總體架構,其大體分爲五個模塊:核心容器、AOP、Web、Data 數據訪問、Test模塊。其中核心容器是 Spring 的核心部分,其它模塊也都依賴於該容器。這裏和就你們一塊兒深刻討論 Spring 的容器,它的做用是什麼、怎麼實現的。java

一、容器簡介

容器顧名思義就是用來裝東西的,裝的是什麼?裝的是 Bean。web

Bean 是 Spring 的基本單位,在基於 Spring 的 Java EE 應用中,全部的組件都被當成 Bean 處理,包括數據源、Hibernate 的 SessionFactory、事務管理器等。在 Spring 中,Bean 是一個很是廣義的概念,任何 Java 對象、Java 組件都被當成 Bean 處理。spring

那容器僅僅是用來保存 Bean 這麼簡單麼?不是。設計模式

當咱們須要使用某個 Bean 時,容器會自動幫咱們建立,並在適當時銷燬。還有一種狀況,當某個 Bean 中需建立另外一個 Bean 時,也就是 Bean 之間有依賴關係,這種依賴的 Bean 也是由容器自動建立。在外界有一個標準的名詞,前者稱呼爲 IOC,也就是控制反轉,後者稱呼爲 DI,也就是依賴注入。安全

IOC/DIsession

IOC (Inversion of Control) 控制反轉:所謂控制反轉,就是當咱們須要某個 Bean 時,將 Bean 的名稱告知容器,由容器去建立該 Bean,而不是咱們手動 new 一個,這裏 Bean 建立管理的控制權都交給了容器,因此這是一種控制權的反轉。其通俗點講就是須要什麼東西讓別人送過來,而不是本身去拿。架構

DI (Dependency Injection) 依賴注入:就是指當 A Bean 裏面需建立 B Bean 時,會在建立 A Bean 的時候,自動將依賴的 B Bean 注入進去,其 B Bean 是被動接受注入而不是本身主動去找。換句話說就是指 A Bean 不是從容器中查找它依賴的 B Bean,而是在容器建立 A Bean 候主動將它依賴的 B Bean 注入給它。app

IOC 和 DI 其實歸根結底實現的功能是相同的,只是一樣的功能站在不一樣的角度來闡述罷了,不過咱們一般喜歡將這兩個概念統稱爲 IOC。固然,在真實場景中,交由 Spring 容器建立的 Bean 泛指在應用程序中的表現層、業務層、持久層等各層對應的 Bean,如 Controller、Service 等;進行數據交互的模型,如 DTO、VO 等就不需交由 Spring 來建立。框架

因此,容器本質上能夠也能夠看做是 Bean 工廠,該工廠管理 Bean 的生命週期,以及 Bean 之間的依賴關係。外界也將 Spring 容器稱爲 IOC 容器。固然,這裏容器僅僅是 Spring 的抽象概念,代碼中將其具象化爲 BeanFactory 或 ApplicationContext,容器功能也由具象化的類進行處理。

二、容器的結構

容器的實現類並非惟一的,Spring 框架提供了多個容器的實現,這些容器分爲兩套體系:一套是早期的 BeanFactory 體系;還有一套是如今經常使用的 ApplicationContext,也可稱爲應用上下文,它繼承了 BeanFactory,它除了有 BeanFactory 的功能外
,還提供了其餘服務,例如事務和 AOP 服務、國際化(il8n)的消息源以及應用程序事件處理等企業級的服務。

說到這,就不得不說 Spring 的兩種配置方式,在早期都是 XML 配置文件的方式,而如今使用的是註解配置的方式。BeanFactory 體系的容器通常用來處理 XML 配置文件的方式,而 ApplicationContext 體系則均可以處理。

2.1 BeanFactory

BeanFactory 是容器最基礎的類,它定義了容器的基本功能規範:

public interface BeanFactory {

    // 對 FactoryBean 的轉義定義,由於若是使用 bean 的名字檢索 FactoryBean 獲得的對象是工廠生成的對象,
    // 若是須要獲得工廠自己,須要轉義(FactoryBean 在後續會詳細介紹)
    String FACTORY_BEAN_PREFIX = "&";
    
    // 根據 bean 的名字,獲取在容器中 bean 實例
    Object getBean(String name) throws BeansException;
    
    //根據 bean 的名字和 Class 類型來獲得 bean 實例,增長了類型安全驗證機制。
    <T> T getBean(String name, @Nullable Class<T> 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 的檢索,看看是否在容器有這個名字的 bean
    boolean containsBean(String name);
    
    // 根據 bean 名字,判斷這個 bean 是否是單例
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    
    // 根據 bean 名字,判斷這個 bean 是否是原型
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    
    // 根據 bean 名字,判斷是否與指定的類型匹配
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
    boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
    
    // 獲得 bean 實例的 Class 類型
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;
    
    // 獲得bean 的別名,若是根據別名檢索,那麼其原名也會被檢索出來
    String[] getAliases(String name);
}

在 BeanFactory 裏只對容器的基本行爲做了定義,其根本不關心你的 Bean 是如何定義怎樣加載的。
正如咱們只關心工廠裏獲得什麼的產品對象,至於工廠是怎麼生產這些對象的,這個基本的接口不關心。而要知道工廠是如何產生對象的,咱們就須要看具體的容器了,也就是 BeanFactory 的子類。

BeanFactory 體系中經常使用的實現類有:

  • ListableBeanFactory:提供容器中 bean 迭代的功能。如返回全部 Bean 的名字、容器中 Bean 的數量等。
  • HierarchicalBeanFactory:提供父容器的訪問功能,可經過 ConfigurableBeanFactory 的 setParentBeanFactory 方法設置父容器。
  • AutowireCapableBeanFactory:爲 Spring 容器以外的 Bean ,也就是未交由 Spring 管理的 Bean ,提供依賴注入的功能。

以上三個是 BeanFactory 的直系親屬,這個三個直系親屬下面又派生了兩個複雜的容器:

  • ConfigurableBeanFactory:其繼承了 HierarchicalBeanFactory 和 SingletonBeanRegistry 這兩個接口,其提供了不少方法,如:定義類加載器、類型轉化、屬性編輯器、註冊依賴 Bean 、銷燬 bean 等,且該接口被大多數的容器繼承、實現。
  • ConfigurableListableBeanFactory:這個接口繼承了 ListableBeanFactory、 AutowireCapableBeanFactory、ConfigurableBeanFactory,自身主要提供用於分析和修改 bean 定義以及預先實例化單例 Bean 的方法。

最後是核心容器:

  • DefaultListableBeanFactory:它實現了以上全部的接口,在 BeanFactory 體系中能夠做爲一個獨立的容器使用。

BeanFactory 大體的繼承關係以下:

BeanFactory 結構

其實之前經常使用的容器是 XmlBeanFactory ,它是 DefaultListableBeanFactory 的實現類,現已被廢除,緣由還未找到,有知道的小夥伴,可在底下留言告知。

但咱們基本不單獨使用 BeanFactory ,而是直接使用 ApplicationContext ,由於 ApplicationContext 包含了 BeanFactory。

2.2 ApplicationContext

上面說過 ApplicationContext 是 BeanFactory 子類,它不只包含 BeanFactory 全部功能,還對其進行了擴展,而咱們喜歡將 ApplicationContext 稱爲應用上下文,由於容器只是它的基本功能。

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
        MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

    // 返回此應用程序上下文的惟一ID
    @Nullable
    String getId();

    // 返回此上下文所屬的應用程序名稱
    String getApplicationName();

    // 返回應用上下文具像化的類名
    String getDisplayName();

    // 返回第一次加載此上下文時的時間戳
    long getStartupDate();

    // 獲取父級應用上下文
    @Nullable
    ApplicationContext getParent();

    // 將 AutowireCapableBeanFactory 接口暴露給外部使用
    AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}

ApplicationContext 自身提供的方法很是簡單,但它繼承了六個接口,來擴展自身功能:

  • EnvironmentCapable:獲取 Environment。
  • ListableBeanFactory、HierarchicalBeanFactory:這是 BeanFactory 體系接口,分別提供 Bean 迭代和訪問父容器的功能。
  • MessageSource:支持國際化功能。
  • ApplicationEventPublisher:應用事件發佈器,封裝事件發佈功能的接口。
  • ResourcePatternResolver:該接口繼承至 ResourceLoader ,做用是加載多個 Resource。

ApplicationContext 一樣提供了很是多的實現類,其又可細分爲兩大類, ConfigurableApplicationContext 和 WebApplicationContext。

2.2.1 ConfigurableApplicationContext

該接口是比較重要的一個接口,幾乎全部的應用上下文都實現了該接口。該接口在ApplicationContext的基礎上提供了配置應用上下文的能力,此外提供了生命週期的控制能力。

public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {

    // 應用上下文配置時,這些符號用於分割多個配置路徑
    String CONFIG_LOCATION_DELIMITERS = ",; \t\n";

    // BeanFactory中,ConversionService類所對應的bean的名字。若是沒有此類的實例的話嗎,則使用默認的轉換規則
    String CONVERSION_SERVICE_BEAN_NAME = "conversionService";

    //LoadTimeWaver類所對應的Bean在容器中的名字。若是提供了該實例,上下文會使用臨時的 ClassLoader ,這樣,LoadTimeWaver就可使用bean確切的類型了 
    String LOAD_TIME_WEAVER_BEAN_NAME = "loadTimeWeaver";

    // Environment 類在容器中實例的名字
    String ENVIRONMENT_BEAN_NAME = "environment";

    // System 系統變量在容器中對應的Bean的名字
    String SYSTEM_PROPERTIES_BEAN_NAME = "systemProperties";

    // System 環境變量在容器中對應的Bean的名字
    String SYSTEM_ENVIRONMENT_BEAN_NAME = "systemEnvironment";

    // 設置容器的惟一ID
    void setId(String id);

    // 設置此容器的父容器
    void setParent(@Nullable ApplicationContext parent);

    // 設置容器的 Environment 變量
    void setEnvironment(ConfigurableEnvironment environment);

    // 以 ConfigurableEnvironment 的形式返回此容器的環境變量。以使用戶更好的進行配置
    @Override
    ConfigurableEnvironment getEnvironment();

    // 此方法通常在讀取應用上下文配置的時候調用,用以向此容器中增長BeanFactoryPostProcessor。增長的Processor會在容器refresh的時候使用。
    void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);

    // 向容器增長一個 ApplicationListener,增長的 Listener 用於發佈上下文事件,如 refresh 和 shutdown 等
    void addApplicationListener(ApplicationListener<?> listener);

    // 向容器中注入給定的 Protocol resolver
    void addProtocolResolver(ProtocolResolver resolver);

    // 這是初始化方法,所以若是調用此方法失敗的狀況下,要將其已經建立的 Bean 銷燬。
    // 換句話說,調用此方法之後,要麼全部的Bean都實例化好了,要麼就一個都沒有實例化
    void refresh() throws BeansException, IllegalStateException;

    // 向JVM註冊一個回調函數,用以在JVM關閉時,銷燬此應用上下文
    void registerShutdownHook();

    // 關閉此應用上下文,釋放其所佔有的全部資源和鎖。並銷燬其全部建立好的 singleton Beans
    @Override
    void close();

    // 檢測此 FactoryBean 是否被啓動過
    boolean isActive();

    // 返回此應用上下文的容器。
    // 千萬不要使用此方法來對 BeanFactory 生成的 Bean 作後置處理,由於單例 Bean 在此以前已經生成。
    // 這種狀況下應該使用 BeanFactoryPostProcessor 來在 Bean 生成以前對其進行處理
    ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
}

該接口下又有幾個重要的實現類:

  • AbstractApplicationContext:這是個抽象類,僅實現了公共的上下文特性。這個抽象類使用了模板方法設計模式,須要具體的實現類去實現這些抽象的方法。
  • GenericApplicationContext:該類繼承自 AbstractApplicationContext,是爲通用目的設計的,它能加載各類配置文件,例如 xml,properties 等等。它的內部持有一個 DefaultListableBeanFactory 的實例,實現了 BeanDefinitionRegistry 接口,以便容許向其應用任何 bean 的定義的讀取器。
  • AnnotationConfigApplicationContext:該類繼承自 GenericApplicationContext ,提供了註解配置(例如:@Configuration、@Component等)和類路徑掃描(scan方法)的支持。

2.2.2 WebApplicationContext

該接口是專門爲 Web 應用準備的,其容許從相對於 Web 根目錄的路徑中裝載配置文件完成初始化。

public interface WebApplicationContext extends ApplicationContext {

    // 整個 Web 應用上下文是做爲屬性放置在 ServletContext 中的,該常量就是應用上下文在 ServletContext 屬性列表中的 key
    String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

    // 定義了三個做用域的名稱
    String SCOPE_REQUEST = "request";
    String SCOPE_SESSION = "session";
    String SCOPE_APPLICATION = "application";

    // 在工廠中的 bean 名稱
    String SERVLET_CONTEXT_BEAN_NAME = "servletContext";

    // ServletContext 初始化參數名稱
    String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";

    // 在工廠中 ServletContext 屬性值環境bean的名稱
    String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";

    // 用來獲取 ServletContext 對象
    @Nullable
    ServletContext getServletContext();
}

該接口的核心實現類有:

  • ConfigurableWebApplicationContext:該接口同時繼承了 WebApplicationContext 和 ConfigurableApplicationContext,提供了 Web 應用上下文的可配置的能力。
  • GenericWebApplicationContext:該類繼承自 GenericApplicationContext,實現了 ConfigurableWebApplicationContext。
  • XmlWebApplicationContext:該上下文是使用 Xml 配置文件的方式,不過是在 Web 環境中使用的。
  • AnnotationConfigServletWebServerApplicationContext:該類是被 SpringBoot 擴展而來的,SpringBoot 使用的就是該上下文。

2.3 差別對比

從上面能夠看出 BeanFactory 是 Sping 框架的基礎接口,通常是面向 Spring 自己;而 ApplicationContext 是以 BeanFactory 爲基礎進行綜合能力擴展,用於知足大型業務應用的建立, ApplicationContext 通常面向使用 Sping 框架的開發者。幾乎全部的應用場合咱們都是直接使用 ApplicationContet 而非底層的 BeanFactory。

下表列出了BeanFactory 和 ApplicationContext 接口和實現所提供的功能:

功能 / 特色 BeanFactory ApplicationContext
Bean 實例化/裝配
BeanPostProcessor 自動註冊 沒有
BeanFactoryPostProcessor 自動註冊 沒有
MessageSource 便捷訪問(針對i18n) 沒有
ApplicationEvent 發佈 沒有

二者還有一個區別是:

  • ApplicationContext 在容器啓動時,一次性建立了全部的 Bean。
  • BeanFactory 在容器啓動時,並未建立 Bean,直到第一次訪問某個 Bean 時才建立目標 Bean。

三、ApplicationContext 準備啓動

在真實環境中,通常經過集成 SSM 或者 SpringBoot 來自動建立 ApplicationContext。

先從 SSM 開始

一、在 web.xml 配置監聽器

<!-- spring監聽器 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

二、容器啓動時會調用 ContextLoaderListener 中的 contextInitialized 方法。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    
    ...
    
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }
    
    ...
}

三、調用父類的 initWebApplicationContext 方法,在該方法中建立、啓動上下文。

public class ContextLoader {
    
    ...
    
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        
        ...

        try {
            
            if (this.context == null) {
            
                // 經過 createWebApplicationContext 方法建立上下文,默認建立 XmlWebApplicationContext
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                if (!cwac.isActive()) {
                    
                    ...
                    // 在該方法中調用上下文的 refresh 方法,refresh 就是啓動上下文的入口
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            ...
        }
        ...
    }
    ...
    
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        
        ... 
        
        wac.refresh();
    }
    
    ...
}

SpringBoot 啓動 ApplicationContext

一、從啓動類開始

@SpringBootApplication
public class DiveInSpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(DiveInSpringBootApplication.class, args);
    }
}

二、找到 SpringApplication 中,最後重載的 run 方法

public ConfigurableApplicationContext run(String... args) {
    
    ...
    
    ConfigurableApplicationContext context = null;
    
    ...
    
    try {
        ...
        // 經過 createApplicationContext 方法建立上下文,根據 Web 環境不一樣建立的上下文也不一樣
        context = createApplicationContext();
    
        ...
        // 該方法用於啓動上下文
        refreshContext(context);
        ...
    
    }
    catch (Throwable ex) {
        ...
    }
    
    context = createApplicationContext();
    
    ...
}

三、進入 refreshContext 方法,裏面調用了 refresh 方法

private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

四、這裏,最終也是調用 ApplicationContext 的 refresh 方法來啓動上下文

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext) applicationContext).refresh();
}

注:這裏主要討論容器的建立和啓動部分,因此省略了其餘部分的代碼。其中 SpringBoot 啓動上下文在前幾篇 《SpringBoot系列》文章有詳細介紹,感興趣的夥伴可自行查閱

能夠看到雖然 SSM 和 SpringBoot 的上下文對象不一樣,但最終都是調用上下文中的 refresh 方法來啓動。該方法是 ApplicationContext 的核心,如 Bean 註冊、注入、解析 XML 、解析註解等是從該方法開始,其內部實現大體以下:

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1. 初始化 refresh 的上下文環境,就是記錄下容器的啓動時間、標記已啓動狀態、處理配置文件中的佔位符
        prepareRefresh();

        // 2. 初始化 BeanFactory,加載並解析配置
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();

        /* ---至此,已經完成了簡單容器的全部功能,下面開始對簡單容器進行加強--- */

        // 3. 對 BeanFactory 進行功能加強,如設置BeanFactory的類加載器,添加幾個 BeanPostProcessor,手動註冊幾個特殊的 bean
        prepareBeanFactory(beanFactory);

        try {
            // 4. 後置處理 beanFactory,交由子類實現
            postProcessBeanFactory(beanFactory);

            // 5. 調用已註冊的 BeanFactoryPostProcessor
            invokeBeanFactoryPostProcessors(beanFactory);

            // 6. 註冊 BeanPostProcessor,僅僅是註冊,調用在getBean的時候
            registerBeanPostProcessors(beanFactory);

            // 7. 初始化國際化資源
            initMessageSource();

            // 8. 初始化事件廣播器
            initApplicationEventMulticaster();

            // 9. 留給子類實現的模板方法
            onRefresh();

            // 10. 註冊事件監聽器
            registerListeners();

            // 11. 實例化全部非延遲加載的單例
            finishBeanFactoryInitialization(beanFactory);

            // 12. 完成刷新過程,發佈應用事件
            finishRefresh();
            
        } catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + ex);
            }
            
            // 13.銷燬已經初始化的 singleton 的 Beans,以避免有些 bean 會一直佔用資源
            this.destroyBeans();
            
            // Reset 'active' flag.
            this.cancelRefresh(ex);
            // Propagate exception to caller.
            throw ex;
        } finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            this.resetCommonCaches();
        }
    }
}

接下來的文章,將對 refresh 方法中的各部分進行詳細討論。

四、總結

最後來作個總體的總結。文章從 Spring 的總體架構開始討論,總體分爲五個模塊:核心容器、AOP、Web、Data 數據訪問、Test模塊,而核心容器又是 Spring 的基礎。容器的做用是什麼?提供 IOC/DI 功能;怎麼實現的?其核心是 BeanFactory 和 ApplicationContext ,通常使用 ApplicationContext ,其包含了 BeanFactory 的全部功能,並對其進行擴展。在 SSM 和 SpringBoot 中自動建立 ApplicationContext 並調用它的 refresh 方法進行啓動,它的 refresh 就是實現容器一系列功能的入口。



以上就是本章內容,若是文章中有錯誤或者須要補充的請及時提出,本人感激涕零。

參考:

https://www.cnblogs.com/09120912zhang/p/7746252.html https://docs.spring.io/spring/docs/5.2.3.RELEASE/spring-framework-reference/core.html#context-introduction https://www.jianshu.com/p/2854d8984dfc https://blog.csdn.net/baidu_36327010/article/details/87983262 https://www.cnblogs.com/zhangfengxian/p/11192054.html https://www.cnblogs.com/sharpest/p/10885820.html

相關文章
相關標籤/搜索