天天看點

二、BeanFactory、ApplicationContext介紹

作者:楓林晚粥

前言

在上篇文章中,和大家一起讨論了 Spring 的整體架構,其大緻分為五個子產品:核心容器、AOP、Web、Data 資料通路、Test子產品。其中核心容器是 Spring 的核心部分,其它子產品也都依賴于該容器。這一篇來深入讨論 Spring的容器,它的作用是什麼、怎麼實作的。

1、容器簡介

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

Bean 是 Spring 的基本機關,在基于 Spring 的 Java EE 應用中,所有的元件都被當成 Bean 處理,包括資料源、Hibernate 的 SessionFactory、事務管理器等。在 Spring 中,Bean 是一個非常廣義的概念,任何Java 對象、Java 元件都被當成 Bean 處理。

那容器僅僅是用來儲存 Bean 這麼簡單麼?不是。

當我們需要使用某個 Bean 時,容器會自動幫我們建立,并在适當時銷毀。還有一種情況,當某個 Bean 中需建立另一個 Bean 時,也就是 Bean 之間有依賴關系,這種依賴的 Bean 也是由容器自動建立。在外界有一個标準的名詞,前者稱呼為 IOC,也就是控制反轉,後者稱呼為DI,也就是依賴注入。

IOC/DI

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 注入給它。

IOC 和 DI 其實歸根結底實作的功能是相同的,隻是同樣的功能站在不同的角度來闡述罷了,不過我們通常喜歡将這兩個概念統稱為 IOC。當然,在真實場景中,交由 Spring 容器建立的 Bean 泛指在應用程式中的表現層、業務層、持久層等各層對應的 Bean,如 Controller、Service 等;進行資料互動的模型,如 DTO、VO 等就不需交由 Spring 來建立。

是以,容器本質上可以也可以看作是 Bean 工廠,該工廠管理 Bean 的生命周期,以及 Bean 之間的依賴關系。外界也将 Spring容器稱為 IOC 容器。當然,這裡容器僅僅是 Spring 的抽象概念,代碼中将其具象化為 BeanFactory 或 ApplicationContext,容器功能也由具象化的類進行處理。

2、容器的結構

容器的實作類并不是唯一的,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、ApplicationContext介紹

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 架構的開發者。幾乎所有的應用場合我們都是直接使用 ApplicationContext 而非底層的 BeanFactory。

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

功能 / 特點 BeanFactory ApplicationContext
Bean 執行個體化/裝配
BeanPostProcessor 自動注冊 沒有
BeanFactoryPostProcessor 自動注冊 沒有
MessageSource 便捷通路(針對i18n) 沒有
ApplicationEvent 釋出 沒有

兩者還有一個差別是:

  • ApplicationContext 在容器啟動時,一次性建立了所有的 Bean。
  • BeanFactory 在容器啟動時,并未建立 Bean,直到第一次通路某個 Bean 時才建立目标 Bean。

3、ApplicationContext 準備啟動

在真實環境中,一般通過內建 SSM 或者 SpringBoot 來自動建立 ApplicationContext。

先從 SSM 開始

1、在 web.xml 配置監聽器

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

           

2、容器啟動時會調用 ContextLoaderListener 中的 contextInitialized 方法。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener{

  @Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

}

           

3、調用父類的 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

1、從啟動類開始

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

           

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

public ConfigurableApplicationContext run(String... args) {

    // ...

    ConfigurableApplicationContext context = null;

    // ...

    try {
        // ...
        // 通過 createApplicationContext 方法建立上下文,根據 Web 環境不同建立的上下文也不同
        context = createApplicationContext();

        // ...
        // 該方法用于啟動上下文
        refreshContext(context);
        // ...

    }
    catch (Throwable ex) {
        // ...
    }

    context = createApplicationContext();

    //...
}

           

3、進入 refreshContext 方法,裡面調用了 refresh 方法

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

           

4、這裡,最終也是調用 ApplicationContext 的 refresh 方法來啟動上下文

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

           
注:這裡主要讨論容器的建立和啟動部分,是以省略了其他部分的代碼。

可以看到雖然 SSM 和 SpringBoot 的上下文對象不同,但最終都是調用上下文中的 refresh 方法來啟動。該方法是 ApplicationContext 的核心,如 Bean 注冊、注入、解析 XML 、解析注解等是從該方法開始,其内部實作大緻如下:

// AbstractApplicationContext
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
						// 此處會掃描base-packages
            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 方法中的各部分進行詳細讨論。

4、總結

最後來做個整體的總結。文章從 Spring 的整體架構開始讨論,整體分為五個子產品:核心容器、AOP、Web、Data 資料通路、Test子產品,而核心容器又是 Spring 的基礎。容器的作用是什麼?提供 IOC/DI 功能;怎麼實作的?其核心是 BeanFactory 和 ApplicationContext ,一般使用 ApplicationContext ,其包含了 BeanFactory 的所有功能,并對其進行擴充。在 SSM 和 SpringBoot 中自動建立 ApplicationContext 并調用它的 refresh 方法進行啟動,它的 refresh 就是實作容器一系列功能的入口。

繼續閱讀