Spring Boot外部化配置實戰解析

 

1、流程分析

1.1 入口程序

在 SpringApplication#run(String... args) 方法中,外部化配置關鍵流程分爲如下四步html

public ConfigurableApplicationContext 

run(String... args) {

    ...

    SpringApplicationRunListeners listeners = getRunListeners(args); // 1

    listeners.starting();

    try {

        ApplicationArguments applicationArguments = new DefaultApplicationArguments(

            args);

        ConfigurableEnvironment environment = prepareEnvironment(listeners,

                                                                 applicationArguments); // 2

        configureIgnoreBeanInfo(environment);

        Banner printedBanner = printBanner(environment);

        context = createApplicationContext();

        exceptionReporters = getSpringFactoriesInstances(

            SpringBootExceptionReporter.class,

            new Class[] { ConfigurableApplicationContext.class }, context);

        prepareContext(context, environment, listeners, applicationArguments,

                       printedBanner); // 3

        refreshContext(context); // 4

        afterRefresh(context, applicationArguments);

        stopWatch.stop();

        if (this.logStartupInfo) {

            new StartupInfoLogger(this.mainApplicationClass)

                .logStarted(getApplicationLog(), stopWatch);

        }

        listeners.started(context);

        callRunners(context, applicationArguments);

    }

    ...

}

 

1.2 關鍵流程思惟導圖

1.3 關鍵流程詳解

對入口程序中標記的四步,分析以下git

1.3.1 SpringApplication#getRunListeners

加載 META-INF/spring.factories github

獲取 SpringApplicationRunListener web

的實例集合,存放的對象是 EventPublishingRunListener 類型 以及自定義的 SpringApplicationRunListener 實現類型spring

1.3.2 SpringApplication#prepareEnvironment

prepareEnvironment 方法中,主要的三步以下bootstrap

private ConfigurableEnvironment 

prepareEnvironment(SpringApplicationRunListeners listeners,

    ApplicationArguments applicationArguments) {

    // Create and configure the environment

    ConfigurableEnvironment environment = getOrCreateEnvironment(); // 2.1

    configureEnvironment(environment, applicationArguments.getSourceArgs()); // 2.2

    listeners.environmentPrepared(environment); // 2.3

    ...

    return environment;

}

 

1) getOrCreateEnvironment 方法

在 WebApplicationType.SERVLET web應用類型下,會建立 StandardServletEnvironment,本文以 StandardServletEnvironment 爲例,類的層次結構以下springboot

當建立 StandardServletEnvironment,StandardServletEnvironment 父類 AbstractEnvironment 調用 customizePropertySources 方法,會執行 StandardServletEnvironment#customizePropertySources和 StandardEnvironment#customizePropertySources ,源碼以下AbstractEnvironmentapp

public AbstractEnvironment() {

    customizePropertySources(this.propertySources);

    if (logger.isDebugEnabled()) {

        logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);

    }

}

 

StandardServletEnvironment#customizePropertySourcesdom

/** Servlet context init parameters property source name: {@value} */

public static final 

StringSERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";

 

/** Servlet config init parameters property source name: {@value} */

public static final String 

SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";

 

/** JNDI property source name: {@value} */

public static final String 

JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";

 

@Override

protected void customizePropertySources(MutablePropertySources propertySources) {

    propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));

    propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));

    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {

        propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));

    }

    super.customizePropertySources(propertySources);

}

 

StandardEnvironment#customizePropertySourceside

/** System environment property source name: {@value} */

public static final String 

SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

 

/** JVM system properties property source name: {@value} */

public static final String 

SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

 

@Override

protected void customizePropertySources(MutablePropertySources propertySources) {

    propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));

    propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,getSystemEnvironment());

}

 

PropertySources 順序:

  • servletConfigInitParams

  • servletContextInitParams

  • jndiProperties

  • systemProperties

  • systemEnvironment

PropertySources 與 PropertySource 關係爲 1 對 N

2) configureEnvironment 方法

調用 configurePropertySources(environment, args), 在方法裏面設置 Environment 的 PropertySources , 包含 defaultProperties 和

SimpleCommandLinePropertySource(commandLineArgs),PropertySources 添加 defaultProperties 到最後,添加 

SimpleCommandLinePropertySource(commandLineArgs)到最前面

PropertySources 順序:

  • commandLineArgs

  • servletConfigInitParams

  • servletContextInitParams

  • jndiProperties

  • systemProperties

  • systemEnvironment

  • defaultProperties

3) listeners.environmentPrepared 方法

會按優先級順序遍歷執行 SpringApplicationRunListener#environmentPrepared,好比 EventPublishingRunListener 和 自定義的 SpringApplicationRunListener

EventPublishingRunListener 發佈

ApplicationEnvironmentPreparedEvent 事件

  • ConfigFileApplicationListener 監聽 

ApplicationEvent 事件 、處理 ApplicationEnvironmentPreparedEvent 事件,加載全部 EnvironmentPostProcessor 包括本身,而後按照順序進行方法回調

---ConfigFileApplicationListener#postProcessEnvironment方法回調 ,而後addPropertySources 方法調用 

RandomValuePropertySource#addToEnvironment,在 systemEnvironment 後面添加 random,而後添加配置文件的屬性源(詳見源碼ConfigFileApplicationListener.Loader#load()

擴展點

  • 自定義 SpringApplicationRunListener ,重寫 environmentPrepared 方法

  • 自定義 EnvironmentPostProcessor

  • 自定義 ApplicationListener 監聽 ApplicationEnvironmentPreparedEvent 事件

  • ConfigFileApplicationListener,便是 EnvironmentPostProcessor ,又是 ApplicationListener ,類的層次結構以下

@Override

public void onApplicationEvent(ApplicationEvent event) {

    // 處理 ApplicationEnvironmentPreparedEvent 事件

    if (event instanceof ApplicationEnvironmentPreparedEvent) {

        onApplicationEnvironmentPreparedEvent(

            (ApplicationEnvironmentPreparedEvent) event);

    }

    // 處理 ApplicationPreparedEvent 事件

    if (event instanceof ApplicationPreparedEvent) {

        onApplicationPreparedEvent(event);

    }

}

private void onApplicationEnvironmentPreparedEvent(

    ApplicationEnvironmentPreparedEvent event) {

    // 加載 META-INF/spring.factories 中配置的 EnvironmentPostProcessor

    List

    // 加載本身 ConfigFileApplicationListener

    postProcessors.add(this);

    // 按照 Ordered 進行優先級排序

    AnnotationAwareOrderComparator.sort(postProcessors);

    // 回調 EnvironmentPostProcessor

    for (EnvironmentPostProcessor postProcessor : postProcessors) {

        postProcessor.postProcessEnvironment(event.getEnvironment(),                                            event.getSpringApplication());

    }

}

List

    return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,                                               getClass().getClassLoader());

}

@Override

public void 

postProcessEnvironment(ConfigurableEnvironment environment,

                                   SpringApplication application) {

    addPropertySources(environment, application.getResourceLoader());

}

 

/**

  * Add config file property sources to the specified environment.

  * @param environment the environment to add source to

  * @param resourceLoader the resource loader

  * @see 

#addPostProcessors(ConfigurableApplicationContext)

  */

protected void 

addPropertySources(ConfigurableEnvironment environment,

                                  ResourceLoader resourceLoader) {

    

RandomValuePropertySource.addToEnvironment(environment);

    // 添加配置文件的屬性源

    new Loader(environment, resourceLoader).load();

}

 

RandomValuePropertySource

public static void 

addToEnvironment(ConfigurableEnvironment environment) {

    // 在 systemEnvironment 後面添加 random

    environment.getPropertySources().addAfter(

        StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,

        new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));

    logger.trace("RandomValuePropertySource add to Environment");

}

 

添加配置文件的屬性源:執行 

new Loader(environment, resourceLoader).load();, 

調用 load(Profile, DocumentFilterFactory, DocumentConsumer)(getSearchLocations() 

獲取配置文件位置,能夠指定經過 spring.config.additional-location 、spring.config.location 、spring.config.name 參數或者使用默認值 ), 而後調用 addLoadedPropertySources -> addLoadedPropertySource(加載 查找出來的 PropertySource 到 PropertySources,並確保放置到 defaultProperties 的前面 )

默認的查找位置,配置爲

"classpath:/,classpath:/config/,file:./,file:./config/",查找順序從後向前

PropertySources 順序:

  • commandLineArgs

  • servletConfigInitParams

  • servletContextInitParams

  • jndiProperties

  • systemProperties

  • systemEnvironment

  • random

  • application.properties ...

  • defaultProperties

1.3.3 SpringApplication#prepareContext

prepareContext 方法中,主要的三步以下

private void 

prepareContext(ConfigurableApplicationContext context,

                            ConfigurableEnvironment environment,

                            SpringApplicationRunListeners listeners,

                            ApplicationArguments applicationArguments,

                            Banner printedBanner) {

    ...

    applyInitializers(context); // 3.1

    listeners.contextPrepared(context); //3.2

    ...

    listeners.contextLoaded(context); // 3.3

}

 

1)applyInitializers 方法

會遍歷執行全部的 ApplicationContextInitializer#initialize

擴展點

  • 自定義 ApplicationContextInitializer
2)listeners.contextPrepared 方法

會按優先級順序遍歷執行 SpringApplicationRunListener#contextPrepared,好比 EventPublishingRunListener 和 自定義的 SpringApplicationRunListener

擴展點

自定義 SpringApplicationRunListener ,重寫 contextPrepared 方法

3)listeners.contextLoaded 方法

會按優先級順序遍歷執行 SpringApplicationRunListener#contextLoaded,好比 EventPublishingRunListener 和 自定義的 SpringApplicationRunListener

EventPublishingRunListener 發佈

ApplicationPreparedEvent 事件

  • ConfigFileApplicationListener 監聽 

ApplicationEvent 事件 處理 

ApplicationPreparedEvent 事件

擴展點

  • 自定義 SpringApplicationRunListener ,重寫 contextLoaded 方法

  • 自定義 ApplicationListener ,監聽 ApplicationPreparedEvent 事件

ConfigFileApplicationListener

@Override

public void onApplicationEvent(ApplicationEvent event) {

    // 處理 ApplicationEnvironmentPreparedEvent 事件

    if (event instanceof 

ApplicationEnvironmentPreparedEvent) {

        onApplicationEnvironmentPreparedEvent(

            (ApplicationEnvironmentPreparedEvent) event);

    }

    // 處理 ApplicationPreparedEvent 事件

    if (event instanceof ApplicationPreparedEvent) {

        onApplicationPreparedEvent(event);

    }

}

 

private void onApplicationPreparedEvent(ApplicationEvent event) {

    this.logger.replayTo(ConfigFileApplicationListener.class);

    addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());

}

 

// 添加 PropertySourceOrderingPostProcessor 處理器,配置 PropertySources

protected void addPostProcessors(ConfigurableApplicationContext context) {

    context.addBeanFactoryPostProcessor(

        new PropertySourceOrderingPostProcessor(context));

}

 

PropertySourceOrderingPostProcessor

// 回調處理(在配置類屬性源解析)

@Override

public void 

postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)

    throws BeansException {

    reorderSources(this.context.getEnvironment());

}

 

// 調整 PropertySources 順序,先刪除 defaultProperties, 再把 defaultProperties 添加到最後

private void reorderSources(ConfigurableEnvironment environment) {

    PropertySource

        .remove(DEFAULT_PROPERTIES);

    if (defaultProperties != null) {

        environment.getPropertySources().addLast(defaultProperties);

    }

}

 

PropertySourceOrderingPostProcessor 是 BeanFactoryPostProcessor

1.3.4 SpringApplication#refreshContext

會進行 @Configuration 配置類屬性源解析,處理 @PropertySource annotations on your @Configuration classes,但順序是在 defaultProperties 以後,下面會把defaultProperties 調整到最後

AbstractApplicationContext#refresh 調用 invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors), 而後進行 BeanFactoryPostProcessor 的回調處理 ,好比 PropertySourceOrderingPostProcessor 的回調(源碼見上文)

PropertySources 順序:

  • commandLineArgs

  • servletConfigInitParams

  • servletContextInitParams

  • jndiProperties

  • systemProperties

  • systemEnvironment

  • random

  • application.properties ...

  • @PropertySource annotations on your @Configuration classes 

  • defaultProperties

(不推薦使用這種方式,推薦使用在 refreshContext 以前準備好,@PropertySource 加載太晚,不會對自動配置產生任何影響)

2、擴展外部化配置屬性源

2.1 基於 EnvironmentPostProcessor 擴展

public class CustomEnvironmentPostProcessor 

implements EnvironmentPostProcessor

 

2.2 基於 ApplicationEnvironmentPreparedEvent 擴展

public class 

ApplicationEnvironmentPreparedEventListener implements ApplicationListener

 

2.3 基於 SpringApplicationRunListener 擴展

public class CustomSpringApplicationRunListener implements SpringApplicationRunListener, Ordered

 

能夠重寫方法 environmentPrepared、contextPrepared、contextLoaded 進行擴展

2.4 基於 ApplicationContextInitializer 擴展

public class CustomApplicationContextInitializer implements ApplicationContextInitializer

 

關於與 Spring Cloud Config Client 整合,對外部化配置加載的擴展(綁定到Config Server,使用遠端的property sources 初始化 Environment),參考源碼PropertySourceBootstrapConfiguration(是對 ApplicationContextInitializer 的擴展)、ConfigServicePropertySourceLocator#locate

獲取遠端的property sources是 RestTemplate 經過向 http://{spring.cloud.config.uri}/{spring.application.name}/{spring.cloud.config.profile}/{spring.cloud.config.label} 發送 GET 請求方式獲取的

2.5 基於 ApplicationPreparedEvent 擴展

public class ApplicationPreparedEventListener 

implements ApplicationListener

 

2.6 擴展實戰

2.6.1 擴展配置

在 classpath 下添加配置文件 META-INF/spring.factories, 內容以下

# Spring Application Run Listeners

org.springframework.boot.SpringApplicationRunListener=\

springboot.propertysource.extend.listener.CustomSpringApplicationRunListener

 

# Application Context Initializers

org.springframework.context.ApplicationContextInitializer=\

springboot.propertysource.extend.initializer.CustomApplicationContextInitializer

 

# Application Listeners

org.springframework.context.ApplicationListener=\

springboot.propertysource.extend.event.listener.ApplicationEnvironmentPreparedEventListener,\

springboot.propertysource.extend.event.listener.ApplicationPreparedEventListener

 

# Environment Post Processors

org.springframework.boot.env.EnvironmentPostProcessor=\

springboot.propertysource.extend.processor.CustomEnvironmentPostProcessor

 

以上的擴展能夠選取其中一種進行擴展,只是屬性源的加載時機不太同樣

2.6.2 擴展實例代碼

https://github.com/shijw823/springboot-externalized-configuration-extend.git

PropertySources 順序:

  • propertySourceName: [ApplicationPreparedEventListener], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [CustomSpringApplicationRunListener-contextLoaded], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [CustomSpringApplicationRunListener-contextPrepared], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [CustomApplicationContextInitializer], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [bootstrapProperties], propertySourceClassName: [CompositePropertySource]

  • propertySourceName: [configurationProperties], propertySourceClassName: [ConfigurationPropertySourcesPropertySource]

  • propertySourceName: [CustomSpringApplicationRunListener-environmentPrepared], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [CustomEnvironmentPostProcessor-dev-application], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [ApplicationEnvironmentPreparedEventListener], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [commandLineArgs], propertySourceClassName: [SimpleCommandLinePropertySource]

  • propertySourceName: [servletConfigInitParams], propertySourceClassName: [StubPropertySource]

  • propertySourceName: [servletContextInitParams], propertySourceClassName: [ServletContextPropertySource]

  • propertySourceName: [systemProperties], propertySourceClassName: [MapPropertySource]

  • propertySourceName: [systemEnvironment], propertySourceClassName: [OriginAwareSystemEnvironmentPropertySource]

  • propertySourceName: [random], propertySourceClassName: [RandomValuePropertySource]

  • propertySourceName: [applicationConfig: [classpath:/extend/config/springApplicationRunListener.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [applicationConfig: [classpath:/extend/config/applicationListener.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [applicationConfig: [classpath:/extend/config/applicationContextInitializer.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [applicationConfig: [classpath:/extend/config/environmentPostProcessor.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [applicationConfig: [classpath:/extend/config/application.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [applicationConfig: [classpath:/extend/config/config.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [applicationConfig: [classpath:/application.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [springCloudClientHostInfo], propertySourceClassName: [MapPropertySource]

  • propertySourceName: [applicationConfig: [classpath:/bootstrap.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

  • propertySourceName: [propertySourceConfig], propertySourceClassName: [ResourcePropertySource]

  • propertySourceName: [defaultProperties], propertySourceClassName: [MapPropertySource]

bootstrapProperties 是 獲取遠端(config-server)的 property sources

加載順序也可參考 http://{host}:{port}/actuator/env

PropertySources 單元測試順序:

  • @TestPropertySource#properties

  • @SpringBootTest#properties

  • @TestPropertySource#locations

3、參考資料

https://docs.spring.io/spring-boot/docs/2.0.5.RELEASE/reference/htmlsingle/#boot-features-external-config

做者:石建偉

來源:宜信技術學院

相關文章
相關標籤/搜索