在 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); } ... }
对入口程序中标记的四步,分析以下git
加载 META-INF/spring.factories github
获取 SpringApplicationRunListener web
的实例集合,存放的对象是 EventPublishingRunListener 类型 以及自定义的 SpringApplicationRunListener 实现类型spring
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; }
在 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
调用 configurePropertySources(environment, args), 在方法里面设置 Environment 的 PropertySources , 包含 defaultProperties 和
SimpleCommandLinePropertySource(commandLineArgs),PropertySources 添加 defaultProperties 到最后,添加
SimpleCommandLinePropertySource(commandLineArgs)到最前面
PropertySources 顺序:
commandLineArgs
servletConfigInitParams
servletContextInitParams
jndiProperties
systemProperties
systemEnvironment
defaultProperties
会按优先级顺序遍历执行 SpringApplicationRunListener#environmentPrepared,好比 EventPublishingRunListener 和 自定义的 SpringApplicationRunListener
EventPublishingRunListener 发布
ApplicationEnvironmentPreparedEvent 事件
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
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 }
会遍历执行全部的 ApplicationContextInitializer#initialize
扩展点
会按优先级顺序遍历执行 SpringApplicationRunListener#contextPrepared,好比 EventPublishingRunListener 和 自定义的 SpringApplicationRunListener
扩展点
自定义 SpringApplicationRunListener ,重写 contextPrepared 方法
会按优先级顺序遍历执行 SpringApplicationRunListener#contextLoaded,好比 EventPublishingRunListener 和 自定义的 SpringApplicationRunListener
EventPublishingRunListener 发布
ApplicationPreparedEvent 事件
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
会进行 @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 加载太晚,不会对自动配置产生任何影响)
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor
public class ApplicationEnvironmentPreparedEventListener implements ApplicationListener
public class CustomSpringApplicationRunListener implements SpringApplicationRunListener, Ordered
能够重写方法 environmentPrepared、contextPrepared、contextLoaded 进行扩展
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 请求方式获取的
public class ApplicationPreparedEventListener implements ApplicationListener
在 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
以上的扩展能够选取其中一种进行扩展,只是属性源的加载时机不太同样
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
做者:石建伟
来源:宜信技术学院