• 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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    2. SpringApplicationRunListeners监听器列表包装类

    	void environmentPrepared(ConfigurableEnvironment environment) {
    		// 循环调用所有的listener
    		for (SpringApplicationRunListener listener : this.listeners) {
    			listener.environmentPrepared(environment);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.EventPublishingRunListener事件发布监听器

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

    这里有点绕,但本质就是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);
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    我们发现关键在创建父容器的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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    6.onApplicationEvent最后的apply方法

    	@Override
    	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    		// ...省略前面
    		apply(context, event.getSpringApplication(), environment);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    前面的监听执行的时候会有一句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);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    4.疑问

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

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

    // don't listen to events in a bootstrap context
    if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
    	return;
    }
    
    • 1
    • 2
    • 3
    • 4
    2. 父容器怎么加载bootstrap文件?

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

    5. 总结

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

  • 相关阅读:
    数字经济崛起,企业数字化转型的底层逻辑
    代码随想录二刷day44
    Spring编程常见错误50例-Spring Bean依赖注入常见错误(上)
    json转化成Map数据结构,Map转Array
    dos汇编总结
    多任务学习(MTL)--学习笔记
    MySql数据库实现注册登录及个人信息查询的数据库设计
    EEG微状态预测并发fMRI动态功能连接状态
    【408考点之数据结构】线性表的顺序表示
    map的一道题目<单词识别>
  • 原文地址:https://blog.csdn.net/qq_40922616/article/details/133658592