本文基于 org.springframework.cloud:spring-cloud-config:2.1.0.RC3
版本进行分析。
1 BootstrapApplicationListener
首先在 spring-cloud-context
包下找到 spring.factories
文件,其中有这样的配置:
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener
程序启动时,会创建 SpringApplication 对象,此时会遍历 spring.factories
文件,将其中的 ApplicationListener
赋值给 listeners
属性。
public class BootstrapApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap";
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;
public static final String DEFAULT_PROPERTIES = "defaultProperties";
private int order = DEFAULT_ORDER;
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
true)) { // 1.1
return;
}
// don't listen to events in a bootstrap context
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
ConfigurableApplicationContext context = null;
String configName = environment
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
.getInitializers()) {
if (initializer instanceof ParentContextApplicationContextInitializer) {
context = findBootstrapContext(
(ParentContextApplicationContextInitializer) initializer,
configName);
}
}
if (context == null) {
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName); // 1.2
event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
}
apply(context, event.getSpringApplication(), environment); // 1.3
}
}
BootstrapApplicationListener
监听 ApplicationEnvironmentPreparedEvent
事件;当执行 SpringApplication->prepareEnvironment
方法时,发布事件,执行 onApplicationEvent
方法。
1、1、 spring.cloud.bootstrap.enabled
默认开启,如果设置为 false 的话,直接返回;
1、2、这里面加载了spring.factories
文件中的BootstrapConfiguration
;
private ConfigurableApplicationContext bootstrapServiceContext(
ConfigurableEnvironment environment, final SpringApplication application,
String configName) {
...
SpringApplicationBuilder builder = new SpringApplicationBuilder()
.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
.environment(bootstrapEnvironment)
// Don't use the default properties in this builder
.registerShutdownHook(false).logStartupInfo(false)
.web(WebApplicationType.NONE);
final SpringApplication builderApplication = builder.application();
...
builder.sources(BootstrapImportSelectorConfiguration.class); // 加载BootstrapConfiguration
final ConfigurableApplicationContext context = builder.run();
...
return context;
}
1、3、把 PropertySourceBootstrapConfiguration
加入到主 SpringApplication 的 initializers 属性中;当调用 SpringApplication -> prepareContext
时,会执行其initialize
方法。
private void apply(ConfigurableApplicationContext context,
SpringApplication application, ConfigurableEnvironment environment) {
@SuppressWarnings("rawtypes")
List<ApplicationContextInitializer> initializers = getOrderedBeansOfType(context,
ApplicationContextInitializer.class);
application.addInitializers(initializers
.toArray(new ApplicationContextInitializer[initializers.size()]));
addBootstrapDecryptInitializer(application);
}
2 PropertySourceBootstrapConfiguration
@Configuration
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME
+ "Properties";
private int order = Ordered.HIGHEST_PRECEDENCE + 10;
@Autowired(required = false)
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
@Override
public int getOrder() {
return this.order;
}
public void setPropertySourceLocators(
Collection<PropertySourceLocator> propertySourceLocators) {
this.propertySourceLocators = new ArrayList<>(propertySourceLocators);
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
CompositePropertySource composite = new CompositePropertySource(
BOOTSTRAP_PROPERTY_SOURCE_NAME); // 2.1
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySourceLocator locator : this.propertySourceLocators) {
PropertySource<?> source = null;
source = locator.locate(environment); // 2.2
if (source == null) {
continue;
}
logger.info("Located property source: " + source);
composite.addPropertySource(source); // 2.3
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); // 2.4
reinitializeLoggingSystem(environment, logConfig, logFile);
setLogLevels(applicationContext, environment);
handleIncludedProfiles(environment);
}
}
}
2、1、新建 name 为 bootstrapProperties 的 PropertySource
;
2、2、解析远程配置文件的内容;
public org.springframework.core.env.PropertySource<?> locate(
org.springframework.core.env.Environment environment) {
ConfigClientProperties properties = this.defaultProperties.override(environment); //获取 ConfigClientProperties
CompositePropertySource composite = new CompositePropertySource("configService");
RestTemplate restTemplate = this.restTemplate == null
? getSecureRestTemplate(properties)
: this.restTemplate;
Exception error = null;
String errorBody = null;
try {
String[] labels = new String[] { "" };
if (StringUtils.hasText(properties.getLabel())) {
labels = StringUtils
.commaDelimitedListToStringArray(properties.getLabel());
}
String state = ConfigClientStateHolder.getState();
// Try all the labels until one works
for (String label : labels) {
Environment result = getRemoteEnvironment(restTemplate, properties,
label.trim(), state); // 获取config-server中的配置文件信息,主要是根据 uri,微服务名,profile 环境,label 拼接 url,之后发送 http 请求获取信息
if (result != null) {
log(result);
if (result.getPropertySources() != null) {
for (PropertySource source : result.getPropertySources()) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) source
.getSource();
composite.addPropertySource(
new MapPropertySource(source.getName(), map)); // 信息保存在 CompositePropertySource 中
}
}
...
return composite;
}
}
}
...
return null;
}
private Environment getRemoteEnvironment(RestTemplate restTemplate,
ConfigClientProperties properties, String label, String state) {
String path = "/{name}/{profile}";
String name = properties.getName();
String profile = properties.getProfile();
...
{
final HttpEntity<Void> entity = new HttpEntity<>((Void) null, headers);
response = restTemplate.exchange(uri + path, HttpMethod.GET, entity,
Environment.class, args); //拼接 url,发送 http 请求,获取 config-server 中的文件信息; 比如 uri=http://um31/config/, path= information-service/dev, 那么最终获取的就是 information-service 服务的 dev 环境的配置文件
...
Environment result = response.getBody();
return result;
}
return null;
}
2、3、将解析后的结果保存到 composite 中;
2、4、将新的配置加入到 env 环境;
private void insertPropertySources(MutablePropertySources propertySources,
CompositePropertySource composite) {
MutablePropertySources incoming = new MutablePropertySources();
incoming.addFirst(composite);
PropertySourceBootstrapProperties remoteProperties = new PropertySourceBootstrapProperties();
Binder.get(environment(incoming)).bind("spring.cloud.config", Bindable.ofInstance(remoteProperties));
// 根据 remoteProperties 的值来确定配置文件的位置
if (!remoteProperties.isAllowOverride() || (!remoteProperties.isOverrideNone()
&& remoteProperties.isOverrideSystemProperties())) {
propertySources.addFirst(composite);
return;
}
if (remoteProperties.isOverrideNone()) {
propertySources.addLast(composite);
return;
}
if (propertySources
.contains(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
if (!remoteProperties.isOverrideSystemProperties()) {
propertySources.addAfter(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
composite);
}
else {
propertySources.addBefore(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
composite);
}
}
else {
propertySources.addLast(composite);
}
}
3 PropertySourceBootstrapProperties
根据PropertySourceBootstrapProperties
来确定配置文件在 enviroment 中的相对位置。
@ConfigurationProperties("spring.cloud.config")
public class PropertySourceBootstrapProperties {
/**
* Flag to indicate that the external properties should override system properties.
* Default true.
*/
private boolean overrideSystemProperties = true;
/**
* Flag to indicate that {@link #isOverrideSystemProperties()
* systemPropertiesOverride} can be used. Set to false to prevent users from changing
* the default accidentally. Default true.
*/
private boolean allowOverride = true;
/**
* Flag to indicate that when {@link #setAllowOverride(boolean) allowOverride} is
* true, external properties should take lowest priority and should not override any
* existing property sources (including local config files). Default false.
*/
private boolean overrideNone = false;
}
- allowOverride 为 false,放最前面;overrideNone 为 false 并且 overrideSystemProperties 为 true,放最前面;
- overrideNone 为 true,放最后面;
- 如果包含 systemEnvironment 配置:overrideSystemProperties 为 false,那么放在 systemEnvironment 后面;否则放在 systemEnvironment 前面;
- 其他情况放在最后面。
比如,设置 spring.cloud.config.overrideSystemProperties=false
,那么 env 中 PropertySource 的次序是怎样的呢?
做个实验,
1、 application.yml 设置 spring.redis.password=gl001
;
2、 主函数中配置 System.setProperty("spring.redis.password", "gl002");
3、 spring.cloud.config
远程配置文件中设置 spring.redis.password=gl003
;
4、 程序启动时设置 --spring.redis.password=gl004
;
public static void main(String[] args) {
System.setProperty("logging.level.org.springframework.core.env", "debug");
System.setProperty("spring.redis.password", "gl002");
ConfigurableApplicationContext context = SpringApplication.run(InfoServiceApplication.class, args);
System.out.println("spring.redis.password: " + context.getEnvironment().getProperty("spring.redis.password"));
Iterator<PropertySource<?>> iter = context.getEnvironment().getPropertySources().iterator();
int i = 0;
while (iter.hasNext()) {
System.out.println((i++) + ":");
PropertySource source = iter.next();
if (source instanceof CompositePropertySource) {
CompositePropertySource compositeSource = (CompositePropertySource) source;
System.out.println("name: " + compositeSource.getName() + ", value: " + compositeSource.getPropertySources());
} else {
System.out.println("name: " + source.getName() + ", value: " + source.getSource());
}
}
}
部分运行结果如下:
spring.redis.password: gl002
4:
name: systemProperties, value: {spring.redis.password=gl002, java.vm.name=Java HotSpot(TM) 64-Bit Server VM, ...}
5:
name: systemEnvironment, value: {spring.redis.password=gl004, SHELL=/bin/zsh, ...}
6:
name: bootstrapProperties, value: [CompositePropertySource {name='configService', propertySources=[MapPropertySource@1306535359 {name='file:/home/config-center/a-service/a-service.yml', properties={spring.redis.password=gl003, ...}]}]
9:
name: applicationConfig: [classpath:/application.yml], value: {spring.redis.password=gl001, server.port=10000,...}