Spring Cloud Config Client配置加载过程分析

首先在 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,...}
复制代码

原文 

https://juejin.im/post/5ef20f3a51882565dc726c08

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Spring Cloud Config Client配置加载过程分析

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址