您好,登錄后才能下訂單哦!
在 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);
}
...
}
對入口程序中標記的四步,分析如下
加載 META-INF/spring.factories
獲取 SpringApplicationRunListener
的實例集合,存放的對象是 EventPublishingRunListener 類型 以及自定義的 SpringApplicationRunListener 實現類型
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;
}
在 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
PropertySources 與 PropertySource 關系為 1 對 N
調用 configurePropertySources(environment, args), 在方法里面設置 Environment 的 PropertySources , 包含 defaultProperties 和
SimpleCommandLinePropertySource(commandLineArgs),PropertySources 添加 defaultProperties 到最后,添加
SimpleCommandLinePropertySource(commandLineArgs)到最前面
PropertySources 順序:
commandLineArgs
servletConfigInitParams
servletContextInitParams
jndiProperties
systemProperties
systemEnvironment
會按優先級順序遍歷執行 SpringApplicationRunListener#environmentPrepared,比如 EventPublishingRunListener 和 自定義的 SpringApplicationRunListener
EventPublishingRunListener 發布
ApplicationEnvironmentPreparedEvent 事件
ApplicationEvent 事件 、處理 ApplicationEnvironmentPreparedEvent 事件,加載所有 EnvironmentPostProcessor 包括自己,然后按照順序進行方法回調
---ConfigFileApplicationListener#postProcessEnvironment方法回調 ,然后addPropertySources 方法調用
RandomValuePropertySource#addToEnvironment,在 systemEnvironment 后面添加 random,然后添加配置文件的屬性源(詳見源碼ConfigFileApplicationListener.Loader#load()
擴展點
自定義 SpringApplicationRunListener ,重寫 environmentPrepared 方法
自定義 EnvironmentPostProcessor
自定義 ApplicationListener 監聽 ApplicationEnvironmentPreparedEvent 事件
@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 ...
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 方法
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
(不推薦使用這種方式,推薦使用在 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]
bootstrapProperties 是 獲取遠端(config-server)的 property sources
加載順序也可參考 http://{host}:{port}/actuator/env
PropertySources 單元測試順序:
@TestPropertySource#properties
@SpringBootTest#properties
https://docs.spring.io/spring-boot/docs/2.0.5.RELEASE/reference/htmlsingle/#boot-features-external-config
作者:石建偉
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。