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
作者:石建偉
來源: 宜信技術學院
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69918724/viewspace-2646566/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Spring Boot 外部化配置實戰解析Spring Boot
- # Spring Boot 外部化配置實戰解析Spring Boot
- 從零開始學Spring Boot系列-外部化配置Spring Boot
- Spring Boot自動配置原理、實戰Spring Boot
- SpringBoot的外部化配置最全解析!Spring Boot
- Spring Boot實戰系列(7)整合Consul配置中心Spring Boot
- [翻譯]Spring Boot 特徵參考2——外部配置:下Spring Boot特徵
- (第四講)Spring Boot 自動化配置原理解析Spring Boot
- Spring Boot 2 實戰:常用讀取配置的方式Spring Boot
- Spring Boot功能實戰Spring Boot
- (最新 9000 字 )Spring Boot 配置特性解析Spring Boot
- 程式設計實戰篇——Spring Boot 自動配置實現程式設計Spring Boot
- Spring Boot:Spring Boot配置MybatisSpring BootMyBatis
- Spring Boot:Spring Boot配置SwaggerSpring BootSwagger
- Spring Boot實戰:模板引擎Spring Boot
- Spring Boot 整合 Elasticsearch 實戰Spring BootElasticsearch
- 《Elasticsearch技術解析與實戰》Chapter 1.4 Spring Boot整合ElasticsearchElasticsearchAPTSpring Boot
- Spring Boot整合Redis實戰操作Spring BootRedis
- Spring Boot & 配置Spring Boot
- Spring Boot專案部署到外部TomcatSpring BootTomcat
- Spring中的Environment外部化配置管理詳解Spring
- Spring Boot系列(四):Spring Boot原始碼解析Spring Boot原始碼
- Spring Boot @EnableAutoConfiguration解析Spring Boot
- SpringBoot外部化配置Spring Boot
- Spring Boot + Mybatis + Spring MVC環境配置(一) :Spring Boot初始化,依賴新增Spring BootMyBatisMVC
- 《Spring Boot 實戰紀實》之需求管理Spring Boot
- Spring Boot應用監控實戰Spring Boot
- Spring Cloud 中自定義外部化擴充套件機制原理及實戰SpringCloud套件
- Spring Boot讀取自定義外部屬性Spring Boot
- Spring Boot讀取外部配置檔案失敗,原因絕對出乎你意料Spring Boot
- spring boot 配置 JPASpring Boot
- Spring Boot核心配置Spring Boot
- 《Spring Boot 實戰紀實》缺失的邏輯Spring Boot
- Spring boot學習(二) Spring boot基礎配置Spring Boot
- Spring Boot學習筆記:Spring Boot核心配置Spring Boot筆記
- Spring Boot系列(三):Spring Boot整合Mybatis原始碼解析Spring BootMyBatis原始碼
- 持續整合之 Spring Boot 實戰篇Spring Boot
- spring boot實戰讀書筆記(1)Spring Boot筆記