在 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
做者:石建偉
來源:宜信技術學院