Spring Boot外部化配置实战解析

简介: 源码解析Spring Boot外部化配置实战。

一、流程分析

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

    }

    ...

}

1.2 关键流程思维导图

1.3 关键流程详解

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

1.3.1 SpringApplication#getRunListeners

加载 META-INF/spring.factories

获取 SpringApplicationRunListener

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

1.3.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;

}
1) getOrCreateEnvironment 方法

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

当创建 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 顺序:

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

三、参考资料

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

作者:石建伟

来源:宜信技术学院

相关文章
|
8月前
|
负载均衡 监控 Java
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
本文详细介绍了 Spring Cloud Gateway 的核心功能与实践配置。首先讲解了网关模块的创建流程,包括依赖引入(gateway、nacos 服务发现、负载均衡)、端口与服务发现配置,以及路由规则的设置(需注意路径前缀重复与优先级 order)。接着深入解析路由断言,涵盖 After、Before、Path 等 12 种内置断言的参数、作用及配置示例,并说明了自定义断言的实现方法。随后重点阐述过滤器机制,区分路由过滤器(如 AddRequestHeader、RewritePath、RequestRateLimiter 等)与全局过滤器的作用范围与配置方式,提
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
|
8月前
|
Java 关系型数据库 MySQL
Spring Boot自动配置:魔法背后的秘密
Spring Boot 自动配置揭秘:只需简单配置即可启动项目,背后依赖“约定大于配置”与条件化装配。核心在于 `@EnableAutoConfiguration` 注解与 `@Conditional` 系列条件判断,通过 `spring.factories` 或 `AutoConfiguration.imports` 加载配置类,实现按需自动装配 Bean。
|
8月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
2758 0
|
7月前
|
前端开发 Java 应用服务中间件
《深入理解Spring》 Spring Boot——约定优于配置的革命者
Spring Boot基于“约定优于配置”理念,通过自动配置、起步依赖、嵌入式容器和Actuator四大特性,简化Spring应用的开发与部署,提升效率,降低门槛,成为现代Java开发的事实标准。
|
7月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
7月前
|
XML JSON Java
【SpringBoot(三)】从请求到响应再到视图解析与模板引擎,本文带你领悟SpringBoot请求接收全流程!
Springboot专栏第三章,从请求的接收到视图解析,再到thymeleaf模板引擎的使用! 本文带你领悟SpringBoot请求接收到渲染的使用全流程!
565 3
|
8月前
|
Java 数据库 数据安全/隐私保护
Spring Boot四层架构深度解析
本文详解Spring Boot四层架构(Controller-Service-DAO-Database)的核心思想与实战应用,涵盖职责划分、代码结构、依赖注入、事务管理及常见问题解决方案,助力构建高内聚、低耦合的企业级应用。
1607 1
|
8月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
1546 5
|
8月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
449 0
探索Spring Boot的@Conditional注解的上下文配置
|
9月前
|
安全 算法 Java
在Spring Boot中应用Jasypt以加密配置信息。
通过以上步骤,可以在Spring Boot应用中有效地利用Jasypt对配置信息进行加密,这样即使配置文件被泄露,其中的敏感信息也不会直接暴露给攻击者。这是一种在不牺牲操作复杂度的情况下提升应用安全性的简便方法。
1490 10

热门文章

最新文章

推荐镜像

更多
  • DNS