spring擴展點之五:ApplicationContextInitializer實現與使用

ApplicationContextInitializer是Spring框架原有的東西,這個類的主要做用就是在ConfigurableApplicationContext類型(或者子類型)的ApplicationContext作refresh以前,容許咱們對ConfiurableApplicationContext的實例作進一步的設置和處理。
 
ApplicationContextInitializer接口是在spring容器刷新以前執行的一個回調函數。是在ConfigurableApplicationContext#refresh() 以前調用(當spring框架內部執行 ConfigurableApplicationContext#refresh() 方法的時候或者在SpringBoot的run()執行時),做用是初始化Spring ConfigurableApplicationContext的回調接口。
 
ApplicationContextInitializer是Spring框架原有的概念, 這個類的主要目的就是在ConfigurableApplicationContext類型(或者子類型)的ApplicationContext作refresh以前,容許咱們對ConfigurableApplicationContext的實例作進一步的設置或者處理。

一般用於須要對應用程序上下文進行編程初始化的web應用程序中。例如,根據上下文環境註冊屬性源或激活概要文件。web

  • 參考ContextLoader和FrameworkServlet中支持定義contextInitializerClasses做爲context-param或定義init-param。
  • ApplicationContextInitializer支持Order註解,表示執行順序,越小越早執行;
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

    /**
     * Initialize the given application context.
     * @param applicationContext the application to configure
     */
    void initialize(C applicationContext);

}

 1、使用分析

該接口典型的應用場景是web應用中須要編程方式對應用上下文作初始化。好比,註冊屬性源(property sources)或者針對上下文的環境信息environment激活相應的profile。spring

在一個Springboot應用中,classpath上會包含不少jar包,有些jar包須要在ConfigurableApplicationContext#refresh()調用以前對應用上下文作一些初始化動做,所以它們會提供本身的ApplicationContextInitializer實現類,而後放在本身的META-INF/spring.factories屬性文件中,這樣相應的ApplicationContextInitializer實現類就會被SpringApplication#initialize發現編程

     // SpringApplication#initialize方法,在其構造函數內執行,從而確保在其run方法以前完成
     private void initialize(Object[] sources) {
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }
        this.webEnvironment = deduceWebEnvironment();
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));//   <===================
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

 而後在應用上下文建立以後,應用上下文刷新(refresh)以前的準備階段被調用 :springboot

SpringBoot內置的一些ApplicationContextInitializer

下面列出了一個使用缺省配置的Springboot web應用默認所使用到的ApplicationContextInitializer實現們:app

DelegatingApplicationContextInitializer
使用環境屬性context.initializer.classes指定的初始化器(initializers)進行初始化工做,若是沒有指定則什麼都不作。框架

經過它使得咱們能夠把自定義實現類配置在application.properties裏成爲了可能。ide

ContextIdApplicationContextInitializer
設置Spring應用上下文的ID,會參照環境屬性。至於Id設置爲啥值會參考環境屬性:
spring.application.name
vcap.application.name
spring.config.name
spring.application.index
vcap.application.instance_index函數

若是這些屬性都沒有,ID使用application。spring-boot

ConfigurationWarningsApplicationContextInitializer
對於通常配置錯誤在日誌中做出警告測試

ServerPortInfoApplicationContextInitializer
將內置servlet容器實際使用的監聽端口寫入到Environment環境屬性中。這樣屬性local.server.port就能夠直接經過@Value注入到測試中,或者經過環境屬性Environment獲取。

SharedMetadataReaderFactoryContextInitializer
建立一個SpringBoot和ConfigurationClassPostProcessor共用的CachingMetadataReaderFactory對象。實現類爲:ConcurrentReferenceCachingMetadataReaderFactory

ConditionEvaluationReportLoggingListener
將ConditionEvaluationReport寫入日誌。

以上都是SpringBoot內置的上文啓動器,可見Spring留出的這個鉤子,被SpringBoot發揚光大了。
實際上不只於此,SpringBoot對Spring Framework的事件監聽機制也都有大量的應用~

總結
ApplicationContextInitializer是Spring留出來容許咱們在上下文刷新以前作自定義操做的鉤子,若咱們有需求想要深度整合Spring上下文,藉助它不乏是一個很是好的實現。

隨便瀏覽一下SpringBoot的源碼可知,它對Spring特徵特性的使用,均是很是的流暢且深度整合的。因此說SpringBoot易學難精的最大攔路虎:實際上是對Spring Framework系統性的把握~

Tips:spring-test包裏有個註解org.springframework.test.context.ContextConfiguration它有個屬性能夠指定ApplicationContextInitializer輔助集成測試時候的自定義對上下文進行預處理~

2、擴展實現方式

2.一、編程方式

先定義ApplicationContextInitializer,以下:

package com.transsnet.palmpay.controller;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;

//@Order的value值越小->越早執行。注:在類上標註,不是方法上
@Order(111)
public class ApplicationContextInitializer1 implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {

        // 打印容器裏面有多少個bean
        System.out.println("bean count=====" + applicationContext.getBeanDefinitionCount());

        // 打印人全部 beanName
        System.out.println(applicationContext.getBeanDefinitionCount() + "個Bean的名字以下:");
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanDefinitionNames) {
            System.out.println(beanName);
        }

    }
}

啓動類裏手動增長initializer

@SpringBootApplication
@EnableConfigServer
@EnableDiscoveryClient
public class ConfigServer {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(ConfigServer.class);

        // 方法一:添加自定義的 ApplicationContextInitializer 實現類的實例(註冊ApplicationContextInitializer)
        springApplication.addInitializers(new ApplicationContextInitializer1());

        ConfigurableApplicationContext context = springApplication.run(args);

        context.close();
    }
}

結果:

2.二、application.properties添加配置方式

對於這種方式是經過DelegatingApplicationContextInitializer這個初始化類中的initialize方法獲取到application.properties中context.initializer.classes對應的類並執行對應的initialize方法。只須要將實現了ApplicationContextInitializer的類添加到application.properties便可。以下:

一、先定義先定義ApplicationContextInitializer,同1.1

二、在application.properties中定義:

2.三、使用spring.factories方式

一、先定義先定義ApplicationContextInitializer,同1.1

二、而後在項目下的resources下新建META-INF文件夾,文件夾下新建spring.factories文件

org.springframework.context.ApplicationContextInitializer=com.dxz.ApplicationContextInitializer1

這個加載過程是在SpringApplication中的getSpringFactoriesInstances()方法中直接加載並實例後執行對應的initialize方法。代碼以下:

    private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }

    private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        Set<String> names = new LinkedHashSet<String>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

3、ApplicationContextInitializer執行順序

springboot中自帶的DelegatingApplicationContextInitializer類的排序值爲0,是springboot自帶的ApplicationContextInitializer中排序最小,最早執行的類。(若是ApplicationContextInitializer沒有實現Orderd接口,那麼其排序值默認是最大,最後執行)

因此能夠獲得其執行順序以下

1.若是咱們經過DelegatingApplicationContextInitializer委託來執行咱們自定義的ApplicationContextInitializer,那麼咱們自定義的ApplicationContextInitializer的順序必定是在系統自帶的其餘ApplicationContextInitializer以前執行。

2.若是咱們經過SpringApplication實例對象調用addInitializers方法加入自定義的ApplicationContextInitializer,那麼spring-boot自帶的ApplicationContextInitializer會先按順序執行,再執行咱們手動添加的自定義ApplicationContextInitializer(按照添加順序執行),最後執行spring-boot自帶的其餘ApplicationContextInializer

3.若是咱們建立本身的spring.factories文件,添加配置加入咱們自定義的ApplicationContextInitializer,那麼咱們自定義的ApplicationContextInitializer會和spring-boot自帶的ApplicationContextInitializer放在一塊兒進行排序執行。

相關文章
相關標籤/搜索