Spring Cloud父子容器及容器的启动源码

1.前言

接触过Spring Cloud都知道,服务启动的时候会先启动Spring Cloud容器加载bootstrap.yml的配置,然后再启动我们常说的Spring容器,那么为什么需要父子容器,父容器又是在什么地方进行创建的呢?

2. 为什么需要父子容器?

这个问题暂时没看到官方的答案,但可以根据父子容器的目的来进行推论一下。

  1. 隔离 Spring Cloud目的是为了解决微服务之间通信时候的一系列问题,它本质并不与具体的业务有关系,所以将它设置为父容器,我们可以将环境独立,不与具体业务环境(Spring容器)进行耦合。
  2. 层级划分 Spring Cloud内的Bean无法引用Spring容器中的Bean,做到容器父子层级划分,避免互相引用的问题。
  3. 业务需求 我们知道远程配置是通过PropertySourceBootstrapConfiguration的初始化执行时处理的,但这个时候我们的bean还未创建,所以无法直接使用。那么这个时候就需要在父容器中提前创建好某些远程配置的服务,满足我们业务需求

如果有其它的原因或者原因不对,也希望大家能留言指出~

3. 父容器创建

Spring Cloud容器的创建是在Spring容器准备阶段创建的,具体我们看到Spring容器启动阶段。

1.prepareEnvironment准备环境

进入到Spring Application的run方法,他是在准备容器阶段prepareEnvironment进行创建的。

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

2. SpringApplicationRunListeners监听器列表包装类

	void environmentPrepared(ConfigurableEnvironment environment) {
		// 循环调用所有的listener
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}

3.EventPublishingRunListener事件发布监听器

	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		// 发布环境准备好的时间,
		this.initialMulticaster
				.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
	}

这里有点绕,但本质就是EventPublishingRunListener这个SpringBoot的监听器会帮忙发布一个事件ApplicationEnvironmentPreparedEvent,然后给对应注册的Spring监听器来处理。

4.SimpleApplicationEventMulticaster发布事件

Spring的事件发布,通过一个multicaster来进行的,会在multicaster里面保存所有的listeners,然后进行调用。

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		// 拿到监听该事件的listener进行invoke调用。同时判断是否需要异步调用,默认都是同步处理
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

Spring Cloud启动时候,默认会有一些监听器,我们找到BoostrapApplicationListener,这个就是我们关键需要用到的Listeners。 在这里插入图片描述

5.BootstrapApplicationListener启动监听器

核心关键就是他的onApplicationEvent方法了

	@Override
	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
		ConfigurableEnvironment environment = event.getEnvironment();
		// 如果没bootstrap启用
		if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
				true)) {
			return;
		}
		// 如果是bootstrap容器则跳过
		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);
			// 子容器增加一个关闭父容器的监听器,在关闭Spring容器的时候,也能关闭父容器
			event.getSpringApplication()
					.addListeners(new CloseContextOnFailureApplicationListener(context));
		}
		// 将父容器的initializer添加到Spring容器中
		apply(context, event.getSpringApplication(), environment);
	}

我们发现关键在创建父容器的bootstrapServiceContext方法中。

private ConfigurableApplicationContext bootstrapServiceContext(
			ConfigurableEnvironment environment, final SpringApplication application,
			String configName) {
		// 创建父容器的环境,参数里面的environment是Spring容器的
		StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
		// 创建时候会默认添加两个配置文件systemEnvironment、systemProperties
		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:}");
		Map<String, Object> bootstrapMap = new HashMap<>();
		bootstrapMap.put("spring.config.name", configName);
		// if an app (or test) uses spring.main.web-application-type=reactive, bootstrap
		// will fail
		// force the environment to use none, because if though it is set below in the
		// builder
		// the environment overrides it
		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);
		}
		// 首先添加boostrap的配置文件
		bootstrapProperties.addFirst(
				new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
		// 添加非stubPropertySources(servlet的都是stub类型),这里拿的是Spring容器的环境配置,前面删除的是父容器创建的环境配置。
		for (PropertySource<?> source : environment.getPropertySources()) {
			if (source instanceof StubPropertySource) {
				continue;
			}
			bootstrapProperties.addLast(source);
		}
		// 创建容器,这里创建的就是Spring Cloud的容器,环境用的是bootstrapEnvironment
		SpringApplicationBuilder builder = new SpringApplicationBuilder()
				.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
				.environment(bootstrapEnvironment)
				.registerShutdownHook(false).logStartupInfo(false)
				.web(WebApplicationType.NONE);
		final SpringApplication builderApplication = builder.application();
		if (builderApplication.getMainApplicationClass() == null) {
			builder.main(application.getMainApplicationClass());
		}
		if (environment.getPropertySources().contains("refreshArgs")) {
			builderApplication
					.setListeners(filterListeners(builderApplication.getListeners()));
		}
		builder.sources(BootstrapImportSelectorConfiguration.class);
		// 启动Spring Cloud容器,从SpringApplication开始重新执行一次启动流程
		final ConfigurableApplicationContext context = builder.run();
		context.setId("bootstrap");
		// 增加祖先初始化器,如果已经存在就把子容器里面的祖先初始化器设置上父容器,否则创建新的
		// 这个初始化器会在子容器初始化时候设置上parent会当前Spring Cloud容器
		addAncestorInitializer(application, context);
		// 将bootstrap配置移除掉,因为下面要合并配置到子容器里面
		bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
		mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
		return context;
	}

6.onApplicationEvent最后的apply方法

	@Override
	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
		// ...省略前面
		apply(context, event.getSpringApplication(), environment);
	}

前面的监听执行的时候会有一句apply方法执行。

	@SuppressWarnings("unchecked")
	private void apply(ConfigurableApplicationContext context,
			SpringApplication application, ConfigurableEnvironment environment) {
		if (application.getAllSources().contains(BootstrapMarkerConfiguration.class)) {
			return;
		}
		// 增加BootstrapMarkerConfiguration到资源中,上一行代码判断已经存在就会直接return,但具体什么情况会发生暂时不太清楚,因为在前面子容器执行就已经return了。
		application.addPrimarySources(Arrays.asList(BootstrapMarkerConfiguration.class));
		// 拿到Spring容器中的初始化器
		@SuppressWarnings("rawtypes")
		Set target = new LinkedHashSet<>(application.getInitializers());
		// 把父容器的初始化器添加进去
		target.addAll(
				getOrderedBeansOfType(context, ApplicationContextInitializer.class));
		application.setInitializers(target);
		// 增加bootstrapdecrpyt初始化器
		addBootstrapDecryptInitializer(application);
	}

本质就是把父容器的初始化器放到子容器中,后续启动时候能使用到。

4.疑问

1.BootstrapApplicationListener会不断创建父容器吗?

不会的,父子容器虽然走的启动流程都是一样的,都会发ApplicationEnvironmentPreparedEvent事件,但监听到后有做过滤,使得父容器不会再执行一次创建父容器的流程。具体在onApplicationEvent中,第二次调用时候会判断环境中是否有bootstrap的配置,如果有就会直接返回了。

// don't listen to events in a bootstrap context
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
	return;
}

2. 父容器怎么加载bootstrap文件?

在BootstrapApplicationListener的bootstrapServiceContext方法执行时,创建父容器之前,往容器中放了一个配置类BootstrapImportSelectorConfiguration,这个类会在类型为beanfactoryPostProcessor的ConfigurationClassPostProcessor扫描配置中执行,和enableautoconfiguration实现同理,只是SPI读取的key不同。

5. 总结

Spring Cloud通过在容器启动时候的环境已准备的事件来进行父容器的创建,父容器的创建很好地隔离开Spring Cloud、Spring Boot应用的环境和配置。

end
  • 作者:Endwas(联系作者)
  • 发表时间:2024-01-14 12:13
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 转载声明:如果是转博主转载的文章,请附上原文链接
  • 公众号转载:请在文末添加作者名字和博客地址
  • 评论