Spring Boot 外部化配置实战解析

石建伟 2018-10-30 浏览量:874

Part.1

流程分析


入口程序


在 SpringApplication#run(String... args) 方法中,外部化配置关键流程分为以下四步


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);

    }

    ...

}


关键流程思维导图


Spring Boot 外部化配置关键流程.png


关键流程详解


对入口程序中标记的四步,分析如下


1

SpringApplication#getRunListeners


加载 META-INF/spring.factories 

获取 SpringApplicationRunListener 

的实例集合,存放的对象是 EventPublishingRunListener 类型 以及自定义的 SpringApplicationRunListener 实现类型

SpringApplictionRunListener.png


2

SpringApplication#prepareEnvironment


prepareEnvironment 方法中,主要的三步如下


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;

}


2.1、getOrCreateEnvironment 方法


在 WebApplicationType.SERVLET web应用类型下,会创建 StandardServletEnvironment,本文以 StandardServletEnvironment 为例,类的层次结构如下


StandardServletEnvironment类层次结构.png


当创建 StandardServletEnvironment,StandardServletEnvironment 父类 AbstractEnvironment 调用 customizePropertySources 方法,会执行 StandardServletEnvironment#customizePropertySources StandardEnvironment#customizePropertySources ,源码如下AbstractEnvironment


public AbstractEnvironment() {

    customizePropertySources(this.propertySources);

    if (logger.isDebugEnabled()) {

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

    }

}


StandardServletEnvironment#customizePropertySources


/** 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#customizePropertySources


/** 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 顺序:

1.     servletConfigInitParams

2.     servletContextInitParams

3.     jndiProperties

4.     systemProperties

5.     systemEnvironment

PropertySources 与 PropertySource 关系为 1 对 N


2.2、configureEnvironment 方法


调用 configurePropertySources(environment, args), 在方法里面设置 Environment 的 PropertySources , 包含 defaultProperties 

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

SimpleCommandLinePropertySource(commandLineArgs)到最前面


PropertySources 顺序:

1.     commandLineArgs

2.     servletConfigInitParams

3.     servletContextInitParams

4.     jndiProperties

5.     systemProperties

6.     systemEnvironment

7.     defaultProperties


2.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 ,类的层次结构如下


ConfigFileApplicationListener类层次结构.png


@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 顺序:

1.     commandLineArgs

2.     servletConfigInitParams

3.     servletContextInitParams

4.     jndiProperties

5.     systemProperties

6.     systemEnvironment

7.     random

8.     application.properties ...

9.     defaultProperties


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

}


3.1、applyInitializers 方法


会遍历执行所有的 ApplicationContextInitializer#initialize


扩展点 

  • 自定义 ApplicationContextInitializer


3.2、listeners.contextPrepared 方法


会按优先级顺序遍历执行 SpringApplicationRunListener#contextPrepared,比如 EventPublishingRunListener 和 自定义的 SpringApplicationRunListener


扩展点 

  • 自定义 SpringApplicationRunListener ,重写 contextPrepared 方法


3.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


4

      SpringApplication#refreshContext


会进行 @Configuration 配置类属性源解析,处理 @PropertySource annotations on your @Configuration classes,但顺序是在 defaultProperties 之后,下面会把defaultProperties 调整到最后

AbstractApplicationContext#refresh 调用 invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors), 然后进行 BeanFactoryPostProcessor 的回调处理 ,比如 PropertySourceOrderingPostProcessor 的回调(源码见上文)


PropertySources 顺序:

1.     commandLineArgs

2.     servletConfigInitParams

3.     servletContextInitParams

4.     jndiProperties

5.     systemProperties

6.     systemEnvironment

7.     random

8.     application.properties ...

9.     @PropertySource annotations on your @Configuration classes 

10.  defaultProperties

不推荐使用这种方式,推荐使用在 refreshContext 之前准备好,@PropertySource 加载太晚,不会对自动配置产生任何影响


Part.2

扩展外部化配置属性源


1

      基于 EnvironmentPostProcessor 扩展


public class CustomEnvironmentPostProcessor 

implements EnvironmentPostProcessor


2

      基于 ApplicationEnvironmentPreparedEvent 扩展


public class 

ApplicationEnvironmentPreparedEventListener implements ApplicationListener


3

      基于 SpringApplicationRunListener 扩展


public class CustomSpringApplicationRunListener implements SpringApplicationRunListener, Ordered

可以重写方法 environmentPrepared、contextPrepared、contextLoaded 进行扩展



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 请求方式获取的


5

基于 ApplicationPreparedEvent 扩展


public class ApplicationPreparedEventListener 

implements ApplicationListener


6

扩展实战


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


以上的扩展可以选取其中一种进行扩展,只是属性源的加载时机不太一样


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


Part.3

参考资料

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



◆ ◆ ◆  ◆ 


发现文章有错误、对内容有疑问,都可以通过关注宜信技术学院微信公众号(CE_TECH),在后台留言给我们。我们每周会挑选出一位热心小伙伴,送上一份精美的小礼品。快来扫码关注我们吧!

WX20180910-152158@2x.png


发现文章有错误、对内容有疑问,都可以通过关注宜信技术学院微信公众号(CE_TECH),在后台留言给我们。我们每周会挑选出一位热心小伙伴,送上一份精美的小礼品。快来扫码关注我们吧!
分享硬核IT 专注金融