Spring Cloud之BootstrapApplicationListener巧妙构建双容器

本文基于:spring-boot-2.3.12-RELEASE、spring-cloud-Hoxton.SR12(即2.2.9-RELEASE)

在看eureka server源码的时候发现spring的refresh方法被执行了两次!开始还以为是自己代码写错了,经过调试发现spring cloud竟然自己启动了一个单独的容器;下面我们来分析一下代码原理;

BootstrapApplicationListener引入时机

接上一篇讲解【springboot事件管理机制】文章 https://blog.csdn.net/Aqu415/article/details/126197702

spring cloud框架提供了的BootstrapApplicationListener会在SpringApplication的构造方法里被解析并缓存进入容器
在这里插入图片描述

类图如下:

在这里插入图片描述

onApplicationEvent 方法分析
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}");
		for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
				.getInitializers()) {
			if (initializer instanceof ParentContextApplicationContextInitializer) {
				context = findBootstrapContext(
						(ParentContextApplicationContextInitializer) initializer,
						configName);
			}
		}
		if (context == null) {
// @A
			context = bootstrapServiceContext(environment, event.getSpringApplication(),
					configName);
			event.getSpringApplication()
					.addListeners(new CloseContextOnFailureApplicationListener(context));
		}

		apply(context, event.getSpringApplication(), environment);
	}

@A:spring cloud 另起炉灶,启动新的spring容器,确切的说是SpringApplication

bootstrapServiceContext方法
private ConfigurableApplicationContext bootstrapServiceContext(
			ConfigurableEnvironment environment, final 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:}");
		String configAdditionalLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
		
		... 省略...
		
		// @A
		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();
		
		... 省略...
		// @B	
		builder.sources(BootstrapImportSelectorConfiguration.class);
		final ConfigurableApplicationContext context = builder.run();
        
        // @C
		addAncestorInitializer(application, context);

        ... 省略...
		bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
		mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
		return context;
	}

@A:构建一个SpringApplicationBuilder对象
@B:设置springapplication的sources属性【偷天换日】复用springboot流程启动一个容器;下面会分析
@C:将spring cloud的容器和main方法启动的容器设置父子关系

	private void addAncestorInitializer(SpringApplication application,
			ConfigurableApplicationContext context) {
		boolean installed = false;
		
... 省略...

		if (!installed) {
// @A
			application.addInitializers(new AncestorInitializer(context));
		}

	}

@A:向application的Initializer里放了一个AncestorInitializer对象,application即springboot原生的应用对象;在外层主流程的prepareContext会调用所有的Initializer的initialize方法
在这里插入图片描述
图中
1、@A是spring cloud的BootstrapApplicationListener执行时机
2、@B是所有Initializer执行时机

在这里插入图片描述
initialize 方法

		@Override
		public void initialize(ConfigurableApplicationContext context) {
			while (context.getParent() != null && context.getParent() != context) {
				context = (ConfigurableApplicationContext) context.getParent();
			}
			reorderSources(context.getEnvironment());
// @A
			new ParentContextApplicationContextInitializer(this.parent)
					.initialize(context);
		}

@A:设置springcloud容器和标准springboot容器的父子关系

BootstrapImportSelectorConfiguration

上面讲了springcloud借助springboot的事件机制将BootstrapApplicationListener注入springboot容器,BootstrapApplicationListener将BootstrapImportSelectorConfiguration注入到springcloud容器;

BootstrapApplicationListener中使用的如下代码:

builder.sources(BootstrapImportSelectorConfiguration.class);

其源码如下:

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * Configuration to import the {@link BootstrapImportSelector} configuration.
 *
 * @author Spencer Gibb
 */
@Configuration(proxyBeanMethods = false)
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {

}

1、首先 BootstrapImportSelectorConfiguration 是一个被@Configuration标注的配置类,并且该类被@Import注解;

2、框架会继续解析 BootstrapImportSelector,解析的时机可以看我之前的文章 【Spring之ConfigurationClassPostProcessor配置类后置处理器源码分析】

BootstrapImportSelector
public class BootstrapImportSelector implements EnvironmentAware, DeferredImportSelector {
...省略...
}

是一个延迟处理ImportSelector,我们来看看它的import方法

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates

// @A
		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;
	}

这个方法主要功能是到spring.factories文件获取key为 org.springframework.cloud.bootstrap.BootstrapConfiguration的配置,即@A 处代码

我们看看一共加载了如下几个类
在这里插入图片描述

名称主要功能
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration处理springcloud的 bootstrap.properties 或者bootstrap.yml 配置文件
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration与属性读取的加解密有关,跟JDK的keystore也有一定的关联
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration和spring常见的解析文件一样的操作
org.springframework.cloud.util.random.CachedRandomPropertySourceAutoConfiguration
org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapConfiguration用RestTemplate与eurekaserver建立连接

但是不是所有的类都会被解析,有的类在不满足条件(@Condition机制)的情况下会被抛弃:如上边的EurekaConfigServerBootstrapConfiguration会被抛弃,因为这是一个eureka client端的类;

小结

从目前代码分析来看,springcloud在个流程过程中启动容器最大的一个作用就是把配置文件加载;然后把自己的容器设置为springboot容器的父容器;

over~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值