springboot源码解读之使用springcloud加载配置

目录

前言

springcloud启动与bootstrap文件加载

springcloud启动

bootstrap文件加载

配置合并

自定义PropertySourceLocator实现任意配置加载

PropertySourceLocator的BeanDefinition注册

PropertySourceLocator加载配置


前言

前面一篇文章https://blog.csdn.net/hongxingxiaonan/article/details/105129792讲了springboot如何加载本地配置。默认情况下,springboot会加载classpath下的application.properties等文件。可能会遇到有的工程里配置了bootstrap.properties也是生效的,但并没有自定义spring boot加载本地配置文件的路径或名字。实际上bootstrap.properties是由spring cloud加载完成的,利用它还可以完成远程配置的加载。

springcloud启动与bootstrap文件加载

springcloud启动

我们知道springboot利用SPI机制提供了很多扩展点,打开spring-cloud-context的spring.factories文件可以看到一系列扩展的实现。其中,BootstrapApplicationListener实现了ApplicationListener

org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener

BootstrapApplicationListener监听了ApplicationEnvironmentPreparedEvent事件,也就是说在spring刚刚创建好environment的时候springcloud启动了。

bootstrap文件加载

BootstrapApplicationListener自己新创建了一个ApplicationContext,并设置了spring.config.name为bootstrap(上一篇文章讲过这个扩展点),所以在新创建的context默认会在location下搜索名字bootstrap的文件。当然,我们也可以用spring.cloud.bootstrap.name自定义文件名,用spring.cloud.bootstrap.location自定义文件路径。

public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
	ConfigurableEnvironment environment = event.getEnvironment();
	//......省略无关代码.....
	ConfigurableApplicationContext context = null;
	String configName = environment
			.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
	//......省略无关代码.....
	if (context == null) {
		context = bootstrapServiceContext(environment, event.getSpringApplication(),
				configName);
		event.getSpringApplication()
				.addListeners(new CloseContextOnFailureApplicationListener(context));
	}

	apply(context, event.getSpringApplication(), environment);
}
private ConfigurableApplicationContext bootstrapServiceContext(
		ConfigurableEnvironment environment, final SpringApplication application,
		String configName) {
	StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
	MutablePropertySources bootstrapProperties = bootstrapEnvironment
			.getPropertySources();
	//......省略无关代码.....
	String configLocation = environment
			.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
	String configAdditionalLocation = environment
			.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
	Map<String, Object> bootstrapMap = new HashMap<>();
	bootstrapMap.put("spring.config.name", configName);	
	bootstrapMap.put("spring.main.web-application-type", "none");
	if (StringUtils.hasText(configLocation)) {
		bootstrapMap.put("spring.config.location", configLocation);
	}
	if (StringUtils.hasText(configAdditionalLocation)) {
		bootstrapMap.put("spring.config.additional-location",
				configAdditionalLocation);
	}
	bootstrapProperties.addFirst(
			new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
	//......省略无关代码.....
	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);
	final ConfigurableApplicationContext context = builder.run();
	
	context.setId("bootstrap");
	addAncestorInitializer(application, context);
	//......省略无关代码.....
	mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
	return context;
}

配置合并

新的context加载完配置之后,需要把配置合并到原来的context的environment中。代码在mergeAdditionalPropertySources方法中,过程为

  • 先创建一个新的ExtendedDefaultPropertySource,用于装新加载的配置。它有个特性就是只能添加OriginTrackedMapPropertySource类型(即ConfigFileApplicationListener中加载的文件)
  • 差分出新context多出的配置,添加到ExtendedDefaultPropertySource中
  • 将ExtendedDefaultPropertySource添加或替换到原context的environment中
private void mergeAdditionalPropertySources(MutablePropertySources environment,
		MutablePropertySources bootstrap) {
	PropertySource<?> defaultProperties = environment.get(DEFAULT_PROPERTIES);
	ExtendedDefaultPropertySource result = defaultProperties instanceof ExtendedDefaultPropertySource
			? (ExtendedDefaultPropertySource) defaultProperties
			: new ExtendedDefaultPropertySource(DEFAULT_PROPERTIES,
					defaultProperties);
	for (PropertySource<?> source : bootstrap) {
		if (!environment.contains(source.getName())) {
			result.add(source);
		}
	}
	for (String name : result.getPropertySourceNames()) {
		bootstrap.remove(name);
	}
	addOrReplace(environment, result);
	addOrReplace(bootstrap, result);
}

自定义PropertySourceLocator实现任意配置加载

在spring程序中,创建Bean的时候需要用到相关的配置项,所以配置的加载总是要先于Bean的创建。

springcloud使用PropertySourceLocator加载配置,并通过实现springboot的两个关键扩展点达到在Bean创建之前加载配置。首先注册一个DeferredImportSelector,它将完成PropertySourceLocator的beanDefinition的注册。然后注册一个springApplication的initializer在prepareContext阶段调用PropertySourceLocator。

PropertySourceLocator的BeanDefinition注册

上面springcloud在创建新的context的时候,有一行代码builder.sources(BootstrapImportSelectorConfiguration.class);

BootstrapImportSelectorConfiguration这个配置类没有别的作用,就是为了引入BootstrapImportSelector

@Configuration(proxyBeanMethods = false)
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {

}

BootstrapImportSelector是一个延迟的beanDefinition加载器DeferredImportSelector,扫描完所有的@Configuration注解之后开始执行。它的selectImports方法实现了beanDefinition查找的逻辑。

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(
			this.environment.getProperty("spring.cloud.bootstrap.sources", ""))));

	List<OrderedAnnotatedElement> elements = new ArrayList<>();
	for (String name : names) {
		try {
			elements.add(
					new OrderedAnnotatedElement(this.metadataReaderFactory, name));
		}
		catch (IOException e) {
			continue;
		}
	}
	AnnotationAwareOrderComparator.sort(elements);

	String[] classNames = elements.stream().map(e -> e.name).toArray(String[]::new);

	return classNames;
}

BootstrapImportSelector通过springboot的SPI工具类SpringFactoriesLoader,查找BootstrapConfiguration的实现类。然后将他们的beanDefinition注册到bootstrap context中。所以我们自定义的配置加载器就可以扩展这个BootstrapConfiguration,如在工程中创建一个META-INF/spring.factories文件,内容为

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.example.demo.CustomRemoteSourceLocator

现在,bootstrap context中已经有了PropertySourceLocator,等待后面来使用。

PropertySourceLocator加载配置

文章开头说到了springcloud的启动过程。springboot启动,然后创建environment并发布EnvironmentPrepared事件,BootstrapApplicationListener监听到消息,创建新的context。在处理EnvironmentPrepared事件的onApplicationEvent方法的最后调用了一个apply方法。

private void apply(ConfigurableApplicationContext context,
      SpringApplication application, ConfigurableEnvironment environment) {
   if (application.getAllSources().contains(BootstrapMarkerConfiguration.class)) {
      return;
   }
   application.addPrimarySources(Arrays.asList(BootstrapMarkerConfiguration.class));
   @SuppressWarnings("rawtypes")
   Set target = new LinkedHashSet<>(application.getInitializers());
   target.addAll(
         getOrderedBeansOfType(context, ApplicationContextInitializer.class));
   application.setInitializers(target);
   addBootstrapDecryptInitializer(application);
}

apply方法从bootstrap context用getBean的方式查询出ApplicationContextInitializer,并补充到SpringApplication中。这里面实际上会添加PropertySourceBootstrapConfiguration,它也是通过上面的BootstrapConfiguration扩展注册到bootstrap context。

spring-cloud-context-2.2.2.RELEASE-sources.jar!/META-INF/spring.factories部分内容:
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration implements
		ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
//...
	@Autowired(required = false)
	private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
}

 getBean会使所有的propertySourceLocator都注入到PropertySourceBootstrapConfiguration中。现在它被创建并完成了注入,并且作为ApplicationContextInitializer被添加到了SpringApplication中。

 SpringApplication在refreshContext之前prepareContext阶段执行所有的ApplicationContextInitializer。在PropertySourceBootstrapConfiguration的initialize方法中,调用所有的propertySourceLocator,并将它们返回的PropertySource添加到environment中。 这样就保证了在bean创建之前配置已经准备好。

public void initialize(ConfigurableApplicationContext applicationContext) {
	List<PropertySource<?>> composite = new ArrayList<>();
	AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
	boolean empty = true;
	ConfigurableEnvironment environment = applicationContext.getEnvironment();
	for (PropertySourceLocator locator : this.propertySourceLocators) {
		Collection<PropertySource<?>> source = locator.locateCollection(environment);
		if (source == null || source.size() == 0) {
			continue;
		}
		List<PropertySource<?>> sourceList = new ArrayList<>();
		for (PropertySource<?> p : source) {
			sourceList.add(new BootstrapPropertySource<>(p));
		}
		logger.info("Located property source: " + sourceList);
		composite.addAll(sourceList);
		empty = false;
	}
	if (!empty) {
		MutablePropertySources propertySources = environment.getPropertySources();
		String logConfig = environment.resolvePlaceholders("${logging.config:}");
		LogFile logFile = LogFile.get(environment);
		for (PropertySource<?> p : environment.getPropertySources()) {
			if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
				propertySources.remove(p.getName());
			}
		}
		insertPropertySources(propertySources, composite);
		reinitializeLoggingSystem(environment, logConfig, logFile);
		setLogLevels(applicationContext, environment);
		handleIncludedProfiles(environment);
	}
}

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值