來源:宜信技術學 college.creditease.cn/ 作者:石建偉
一、流程分析
入口程式
在 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、SpringApplication#getRunListeners
載入 META-INF/spring.factories
獲取 SpringApplicationRunListener
的例項集合,存放的物件是 EventPublishingRunListener
型別 以及自定義的 SpringApplicationRunListener
實現型別
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
,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 String SERVLET_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.2、configureEnvironment
方法
呼叫 configurePropertySources(environment, args)
, 在方法裡面設定 Environment
的 PropertySources
, 包含 defaultProperties
和 SimpleCommandLinePropertySource
(commandLineArgs),PropertySources
新增 defaultProperties
到最後,新增 SimpleCommandLinePropertySource
(commandLineArgs)到最前面
PropertySources
順序:
-
commandLineArgs
-
servletConfigInitParams
-
servletContextInitParams
-
jndiProperties
-
systemProperties
-
systemEnvironment
-
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
,類的層次結構如下
@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<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
// 載入自己 ConfigFileApplicationListener
postProcessors.add(this);
// 按照 Ordered 進行優先順序排序
AnnotationAwareOrderComparator.sort(postProcessors);
// 回撥 EnvironmentPostProcessor
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
List<EnvironmentPostProcessor> loadPostProcessors() {
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
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<?> defaultProperties = environment.getPropertySources()
.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
順序:
-
commandLineArgs
-
servletConfigInitParams
-
servletContextInitParams
-
jndiProperties
-
systemProperties
-
systemEnvironment
-
random
-
application.properties ...
-
@PropertySource
annotations on your@Configuration
classes -
defaultProperties
不推薦使用這種方式,推薦使用在 refreshContext 之前準備好,
@PropertySource
載入太晚,不會對自動配置產生任何影響
二、擴充套件外部化配置屬性源
1、基於 EnvironmentPostProcessor
擴充套件
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor
複製程式碼
2、基於 ApplicationEnvironmentPreparedEvent
擴充套件
public class ApplicationEnvironmentPreparedEventListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>
複製程式碼
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<ApplicationPreparedEvent>
複製程式碼
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、擴充套件例項程式碼
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
複製程式碼