承接前文監聽器對bootstrapContext創建的引導,筆者了解到其主要入口類為BootstrapImportSelectorConfiguration。本文將基於此類進行簡單的分析
BootstrapImportSelectorConfiguration
簡單的配置類,看下源碼
@Configuration
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {
}
嗯,引入了延遲加載類BootstrapImportSelector,那筆者就繼續往下看下此會延遲加載哪些類,直接去觀察其主方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
List<String> names = new ArrayList<>(SpringFactoriesLoader
.loadFactoryNames(BootstrapConfiguration.class, classLoader));
names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(
environment.getProperty("spring.cloud.bootstrap.sources", ""))));
List<OrderedAnnotatedElement> elements = new ArrayList<>();
for (String name : names) {
try {
elements.add(new OrderedAnnotatedElement(metadataReaderFactory, name));
} catch (IOException e) {
continue;
}
}
AnnotationAwareOrderComparator.sort(elements);
String[] classNames = elements.stream()
.map(e -> e.name)
.toArray(String[]::new);
return classNames;
}
上述的代碼很簡單,其會去加載classpath路徑下所有spring.factories文件中以org.springframework.cloud.bootstrap.BootstrapConfiguration作為Key的所有類;
同時springcloud也支持通過設置spring.cloud.bootstrap.sources屬性來加載指定類
筆者就先以springcloud context板塊內的spring.factories作為分析的源頭
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
在分析上述的源碼之前,筆者必須得清楚現在bootstrapContext加載的配置文件默認為bootstrap.properties抑或是bootstrap.yml,其屬性已經被加入至相應的Environment對象中。基於此我們再往下走,以免犯糊塗
PropertySourceBootstrapConfiguration
配置源的加載,此Configuration主要用於確定是否外部加載的配置屬性復寫Spring內含的環境變量。注意其是ApplicationContextInitializer接口的實現類,前文已經提到,bootstrapContext上的此接口的bean類都會被注冊至子級的SpringApplication對象上。
直接看下主要的代碼片段把
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
CompositePropertySource composite = new CompositePropertySource(
BOOTSTRAP_PROPERTY_SOURCE_NAME);
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
// 此處為子級的環境變量對象
ConfigurableEnvironment environment = applicationContext.getEnvironment();
// 通過PropertySourceLocator接口去加載外部配置
for (PropertySourceLocator locator : this.propertySourceLocators) {
PropertySource<?> source = null;
source = locator.locate(environment);
if (source == null) {
continue;
}
logger.info("Located property source: " + source);
composite.addPropertySource(source);
empty = false;
}
if (!empty) {
MutablePropertySources propertySources = environment.getPropertySources();
String logConfig = environment.resolvePlaceholders("${logging.config:}");
LogFile logFile = LogFile.get(environment);
if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
}
// 確定屬性讀取的先后順序
insertPropertySources(propertySources, composite);
// reinitialize log
reinitializeLoggingSystem(environment, logConfig, logFile);
setLogLevels(applicationContext, environment);
// active profiles process
handleIncludedProfiles(environment);
}
}
上述的代碼就涉及兩點,一個是通過PropertySourceLocator接口加載外部配置;一個是用於解析以spring.cloud.config為開頭的PropertySourceBootstrapProperties屬性,默認情況下,外部配置比內部變量有更高的優先級。具體的用戶可自行分析
備注:PropertiesSourceBootstrapProperties中的屬性變量可通過PropertySourceLocator接口配置
PropertyPlaceholderAutoConfiguration
和spring常見的解析文件一樣的操作,具體就不分析了
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PropertyPlaceholderAutoConfiguration {
// 配置文件屬性讀取常用類
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
ConfigurationPropertiesRebinderAutoConfiguration
通過命名便會發現其跟刷新屬性的功能有關,先優先看下其類結構
@Configuration
@ConditionalOnBean(ConfigurationPropertiesBindingPostProcessor.class)
public class ConfigurationPropertiesRebinderAutoConfiguration
implements ApplicationContextAware, SmartInitializingSingleton {
}
上述代碼表示其依據於當前類環境存在ConfigurationPropertiesBindingPostProcessorBean對象才會被應用,仔細查閱了下,發現只要有使用到@EnableConfigurationProperties注解即就會被注冊。看來此配置跟ConfigurationProperties注解也有一定的關聯性。
本文就羅列筆者比較關注的幾個地方
1.ConfigurationPropertiesRebinder對象的創建
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public ConfigurationPropertiesRebinder configurationPropertiesRebinder(
ConfigurationPropertiesBeans beans) {
ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder(
beans);
return rebinder;
}
此類讀者可以自行翻閱代碼,可以發現其暴露了JMX接口以及監聽了springcloud context自定義的EnvironmentChangeEvent事件。看來其主要用來刷新ApplicationContext上的beans(含@ConfigurationProperties注解)對象集合
2.ConfigurationPropertiesBeans對象的創建
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public ConfigurationPropertiesBeans configurationPropertiesBeans() {
//
ConfigurationBeanFactoryMetadata metaData = this.context.getBean(
ConfigurationBeanFactoryMetadata.BEAN_NAME,
ConfigurationBeanFactoryMetadata.class);
ConfigurationPropertiesBeans beans = new ConfigurationPropertiesBeans();
beans.setBeanMetaDataStore(metaData);
return beans;
}
配合ConfigurationProperties注解的表演,其會緩存ApplicationContext上的所有含有ConfigurationProperties注解的bean。與第一點所提的ConfigurationPropertiesRebinder對象搭配使用
3.實例化結束后刷新父級ApplicationContext上的屬性
@Override
public void afterSingletonsInstantiated() {
// refresh parent application context
if (this.context.getParent() != null) {
// TODO: make this optional? (E.g. when creating child contexts that prefer to
// be isolated.)
ConfigurationPropertiesRebinder rebinder = context
.getBean(ConfigurationPropertiesRebinder.class);
for (String name : context.getParent().getBeanDefinitionNames()) {
rebinder.rebind(name);
}
}
}
EncryptionBootstrapConfiguration
與屬性讀取的加解密有關,跟JDK的keystore也有一定的關聯,具體就不去解析了。讀者可自行分析
Bean加載問題
此處本文插入這個Bean的加載問題,因為筆者發現SpringApplication會被調用兩次,那么ApplicationContext實例也會被創建兩次。那么基於@Configuration修飾過的自定義的Bean是不是也會被加載兩次呢??
經過在cloud環境下編寫了一個簡單的Bean
package com.example.clouddemo;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
/**
* @author nanco
* -------------
* -------------
* @create 19/8/20
*/
@Configuration
public class TestApplication implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("application parent context id: " + applicationContext.getParent().getId());
System.out.println("application context id: " + applicationContext.getId());
}
}
且在application.properties文件中指定spring.application.name=springChild以及bootstrap.properties文件中也指定spring.application.name=springBootstrap
運行main方法之后打印的關鍵信息如下
application parent context id: bootstrap
application context id: springBootstrap-1
經過在org.springframework.boot.context.ContextIdApplicationContextInitializer類上進行斷點調試發現只有用戶級ApplicationContext被創建的過程中會實例化用戶自定義Bean。也就是說bootstrapContext並不會去實例化用戶自定義的Bean,這樣就很安全。
那么為何如此呢?其實很簡單,因為bootstrapContext指定的source類只有BootstrapImportSelectorConfiguration,並沒有用戶編寫的啟動類,也就無法影響用戶級別Context的Bean加載實例化了~~~並且該類上無@EnableAutoConfiguration注解,表明其也不會去處理spring.factories文件中@EnableAutoConfiguration注解key對應的配置集合。
小結
1.針對springcloud context模塊下的以BootstrapConfiguration作為Key的自動配置類,除了PropertySourceBootstrapConfiguration自動類的應用范圍在子級ApplicationContext,其它三個均有作用於父級ApplicationContext。
2.關於外部源文件的屬性,默認情況下其有更高的優先級於本地系統以及環境變量。當然用戶也可以通過修改spring.cloud.config.allowOverride/spring.cloud.config.overrideSystemProperties/spring.cloud.config.overrideNone屬性來進行優先級更改,通過此,用戶需要復寫PropertySourceLocator接口來進行配置
package com.example.cloud.external.resource;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import java.util.HashMap;
import java.util.Map;
/**
* @author nanco
* -------------
* cloud-demo
* -------------
* @create 2019/1/15 19:40
* @descrption
**/
@Configuration
public class ExternalPropertySourceLocator implements PropertySourceLocator {
private static final String EXTERNAL_KEY = "external";
@Override
public PropertySource<?> locate(Environment environment) {
Map<String, Object> externalMap = new HashMap<>();
// user custom property
externalMap.put("username", "nanco");
externalMap.put("password", "nanco123");
externalMap.put("mail", "nancoasky@gmail.com");
// application property
externalMap.put("spring.cloud.config.allowOverride", true);
externalMap.put("spring.cloud.config.overrideSystemProperties", false); //system擁有更高的優先級
externalMap.put("spring.cloud.config.overrideNone", false);
return new MapPropertySource(EXTERNAL_KEY, externalMap);
}
}
最后將上述的類放置在META-INF/spring.factories文件中便可以生效了
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.example.cloud.external.resource.ExternalPropertySourceLocator
3.關於針對@ConfigurationProperties
注解的Beans對象的刷新操作,本文只講解了JMX方式去調用,如果與第三方插件結合,應該會有更多的形式。
4.針對監聽器環節的分析到本章暫時告一段落,下一篇便分析cloud-context板塊spring.factories文件中以org.springframework.boot.autoconfigure.EnableAutoConfiguration作為Key的類集合。
熟悉的氣息再次降臨,迫不及待ing....