來源:[宜信技術學院 ] html
做者:石建偉java
在 SpringApplication#run(String... args)
方法中,外部化配置關鍵流程分爲如下四步git
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); } ... }
對入口程序中標記的四步,分析以下github
SpringApplication#getRunListeners
加載 META-INF/spring.factories
獲取 SpringApplicationRunListener
的實例集合,存放的對象是 EventPublishingRunListener
類型 以及自定義的 SpringApplicationRunListener
實現類型web
SpringApplication#prepareEnvironment
prepareEnvironment
方法中,主要的三步以下spring
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.一、getOrCreateEnvironment
方法bootstrap
在 WebApplicationType.SERVLET
web應用類型下,會建立 StandardServletEnvironment
,本文以 StandardServletEnvironment
爲例,類的層次結構以下springboot
當建立 StandardServletEnvironment
,StandardServletEnvironment
父類 AbstractEnvironment
調用 customizePropertySources
方法,會執行 StandardServletEnvironment#customizePropertySources
和 StandardEnvironment#customizePropertySources
,源碼以下app
AbstractEnvironment
dom
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
順序:
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
2.三、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
順序:
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.一、applyInitializers
方法
會遍歷執行全部的 ApplicationContextInitializer#initialize
ApplicationContextInitializer
3.二、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<?> defaultProperties = environment.getPropertySources() .remove(DEFAULT_PROPERTIES); if (defaultProperties != null) { environment.getPropertySources().addLast(defaultProperties); } }
PropertySourceOrderingPostProcessor
是BeanFactoryPostProcessor
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
加載太晚,不會對自動配置產生任何影響
EnvironmentPostProcessor
擴展public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor
ApplicationEnvironmentPreparedEvent
擴展public class ApplicationEnvironmentPreparedEventListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>
SpringApplicationRunListener
擴展public class CustomSpringApplicationRunListener implements SpringApplicationRunListener, Ordered
能夠重寫方法 environmentPrepared、contextPrepared、contextLoaded 進行擴展
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 請求方式獲取的
ApplicationPreparedEvent
擴展public class ApplicationPreparedEventListener implements ApplicationListener<ApplicationPreparedEvent>
在 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