SpringBoot之父子容器监听器BootstrapApplicationListener

对于SpringBoot中的属性文件application.properties和application.yml文件应该非常熟悉,但是对于bootstrap.properties文件和bootstrap.yml这个两个文件用的估计就比较少了,用过的应该清楚bootstrap.properties中定义的文件信息会先与application.properties中的信息加载。

首先在SpringBoot中默认是不支持bootstrap.properties属性文件的。我们需要引入SpringCloud的依赖spring-cloud-starter-bootstrap或者存在spring-cloud-context包,在这些包中spring.factories文件中配置类监听器BootstrapApplicationListener

根据文章监听器得知,在众多org.springframework.context.ApplicationListener类型的监听器中,BootstrapApplicationListener监听器是最先触发执行的。ConfigFileApplicationListener监听器是SpringBoot启动过程中正常加载配置文件的监听器。

public class BootstrapApplicationListener{
	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
		ConfigurableEnvironment environment = event.getEnvironment();
		if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,true)) {
			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}");//#1
		for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
				.getInitializers()) {
			if (initializer instanceof ParentContextApplicationContextInitializer) {
				context = findBootstrapContext(
						(ParentContextApplicationContextInitializer) initializer,
						configName);
			}
		}
		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,SpringApplication application,String configName) {
								
		StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
		MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
		for (PropertySource<?> source : bootstrapProperties) {
			bootstrapProperties.remove(source.getName());
		}
		String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.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);
		}
		...
		SpringApplicationBuilder builder = new SpringApplicationBuilder()
				.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
				.environment(bootstrapEnvironment)
				.registerShutdownHook(false).logStartupInfo(false)
				.web(WebApplicationType.NONE);
		SpringApplication builderApplication = builder.application();
		...
		builder.sources(BootstrapImportSelectorConfiguration.class);//#2
		final ConfigurableApplicationContext context = builder.run();
		addAncestorInitializer(application, context);//#3
		...
		return context;
	}
}

步骤1:监听器从环境变量获取spring.cloud.bootstrap.name配置项,否则为默认配置文件名bootstrap
步骤3:设置父子容器。application创建常规容器AnnotationConfigServletWebServerApplicationContext,并将第二个容器AnnotationConfigApplicationContext设置为其父容器。

1、BootstrapImportSelectorConfiguration

BootstrapImportSelector类似于正常启动类通过@Import导入的AutoConfigurationImportSelector。

@Configuration(proxyBeanMethods = false)
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {}
public class BootstrapImportSelector implements EnvironmentAware, DeferredImportSelector {
	...
	@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));//#1
		names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(
				this.environment.getProperty("spring.cloud.bootstrap.sources", ""))));

		List<OrderedAnnotatedElement> elements = new ArrayList<>();
		for (String name : names) {
			elements.add(new OrderedAnnotatedElement(this.metadataReaderFactory, name));
		}
		AnnotationAwareOrderComparator.sort(elements);
		String[] classNames = elements.stream().map(e -> e.name).toArray(String[]::new);
		return classNames;
	}
}

步骤1:通过SPI机制获取org.springframework.cloud.bootstrap.BootstrapConfiguration接口的子类。其中比较熟悉的子类为ConsulConfigBootstrapConfiguration

2、父子容器之AncestorInitializer

父容器ConsulConfigBootstrapConfiguration是通过AncestorInitializer被添加为AnnotationConfigServletWebServerApplicationContext的父容器。

private void addAncestorInitializer(SpringApplication application,
		ConfigurableApplicationContext context) {
	boolean installed = false;
	for (ApplicationContextInitializer<?> initializer : application
			.getInitializers()) {
		if (initializer instanceof AncestorInitializer) {
			installed = true;
			// New parent
			((AncestorInitializer) initializer).setParent(context);
		}
	}
	if (!installed) {
		application.addInitializers(new AncestorInitializer(context));
	}
}

AncestorInitializer的回调:SpringApplication#run~prepareContext

SpringBoot解析启动类过程中如果存在父容器则优先从父容器获取当前bean

3、ConsulConfigBootstrapConfiguration

在SpringBoot通过BootstrapApplicationListener监听器加载bootstrap配置文件过程中,通过延迟加载策略实现自动装配候选类的加载。

@Configuration(proxyBeanMethods = false)
@ConditionalOnConsulEnabled
public class ConsulConfigBootstrapConfiguration {

	@Configuration(proxyBeanMethods = false)
	@EnableConfigurationProperties
	@Import(ConsulAutoConfiguration.class)
	@ConditionalOnProperty(name = "spring.cloud.consul.config.enabled",
			matchIfMissing = true)
	protected static class ConsulPropertySourceConfiguration {

		@Autowired
		private ConsulClient consul;

		@Bean
		@ConditionalOnMissingBean
		public ConsulConfigProperties consulConfigProperties() {
			return new ConsulConfigProperties();
		}

		@Bean
		public ConsulPropertySourceLocator consulPropertySourceLocator(
				ConsulConfigProperties consulConfigProperties) {
			return new ConsulPropertySourceLocator(this.consul, consulConfigProperties);
		}
	}
}

在Consul源码相关分析中关于spring.cloud.consul相关的配置信息是映射于ConsulProperties。ConsulProperties是通过ConsulAutoConfiguration间接引入的。而ConsulAutoConfiguration是在解析bootstrap配置文件时通过当前配置类间接引入的,所以ConsulProperties的属性值是解析bootstrap配置文件得到的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值