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

1.前言

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

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

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

  1. 隔离
    Spring Cloud目的是为了解决微服务之间通信时候的一系列问题,它本质并不与具体的业务有关系,所以将它设置为父容器,我们可以将环境独立,不与具体业务环境(Spring容器)进行耦合。
  2. 层级划分
    Spring Cloud内的Bean无法引用Spring容器中的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) {// 循环调用所有的listenerfor (SpringApplicationRunListener listener : this.listeners) {listener.environmentPrepared(environment);}}

3.EventPublishingRunListener事件发布监听器

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

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

4.SimpleApplicationEventMulticaster发布事件

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

	@Overridepublic 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方法了

	@Overridepublic 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、systemPropertiesMutablePropertySources 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 itbootstrapMap.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的容器,环境用的是bootstrapEnvironmentSpringApplicationBuilder 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方法

	@Overridepublic 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到资源中,我看这个是没有任何代码的类,具体不太清楚目的是何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.prepareEnvironment不会在父容器也执行吗?

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

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

5. 总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/128120.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JS-前端在dom中预览pdf等文件

1、将pdf等文件显示到dom元素中预览 pdf文件可以是blob、url、file类型等只要使用URL.createObjectURL(file)全部转为URL即可使用无需借助任何插件&#xff0c;只需要使用<object></object>标签即可实现 1.1、html <template><div class"home"…

云原生Kubernetes:K8S集群kubectl命令汇总

目录 一、理论 1.概念 2. kubectl 帮助方法 3.kubectl 子命令使用分类 4.使用kubectl 命令的必要环境 5.kubectl 详细命令 一、理论 1.概念 kubectl是一个命令行工具&#xff0c;通过跟 K8S 集群的 API Server 通信&#xff0c;来执行集群的管理工作。 kubectl命令是操…

对于无法直接获取URL的数据爬虫

在爬学校安全教育题库的时候发现题库分页实际上执行了一段js代码&#xff0c;如下图所示 点击下一页时是执行了函数doPostBack&#xff0c;查看页面源码如下 点击下一页后这段js提交了一个表单&#xff0c;随后后端返回对应数据&#xff0c;一开始尝试分析获取对应两个参数&a…

CCF CSP认证 历年题目自练Day25

题目 试题编号&#xff1a; 201403-3 试题名称&#xff1a; 命令行选项 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 256.0MB 问题描述&#xff1a; 问题描述   请你写一个命令行分析程序,用以分析给定的命令行里包含哪些选项。每个命令行由若干个字符串组成,它们之间…

Unity基于种子与地块概率的开放世界2D地图生成

public class BuildingGen : MonoBehaviour {public int[] Building;//存储要生成的地块代码public int[] Probability;//存储概率public double seed;public int width 100;public int height 100;public float noiseScale 0.1f; //噪声缩放倍数private int[,] frequencyM…

axios的get请求时数组参数没有下标

开发新项目过程中 发现get请求时 数组参数没有下标 这样肯定是不行的 后端接口需要数组[0]: 7 数组[1]:4这样的数据 原因是因为在请求拦截器没有处理需要的参数 解决方法 在请求拦截器 处理一下参数 import axios, { AxiosError, AxiosInstance, AxiosRequestHeaders } fro…

制作长图海报的详细指南,制作长图海报的5个步骤

制作长图海报是宣传活动、产品或服务的重要方式之一。乔拓云后台提供了丰富的海报模板&#xff0c;让你轻松制作出专业级的长图海报。下面将介绍如何使用乔拓云后台制作长图海报的技巧。 一、选择模板 首先&#xff0c;注册并登录乔拓云后台&#xff0c;进入云设计页面。在选择…

第三次作业

自己实现扫描全盘的函数 def scan_disk(dir): global count,dir_count if os.path.isdir(dir): files os.listdir(dir) for file in files: print(file) dir_count 1 if os.path.isdir(dir os.sep file): …

Git从本地库撤销已经添加的文件或目录

场景 在提交时, 误将一个目录添加到了暂存区, 而且commit 了本地库,同批次commit 的还有其他需要提交的文件。 commit 之后发现这个目录下所有的文件都不需要提交, 现在需要撤销这个提交, 使这个目录不被push到远端库。 这里以远端服务器github 为例,在Git GUI下看到的…

腾讯云轻量和CVM有啥区别?怎么选择服务器配置?

腾讯云轻量服务器和云服务器有什么区别&#xff1f;为什么轻量应用服务器价格便宜&#xff1f;是因为轻量服务器CPU内存性能比云服务器CVM性能差吗&#xff1f;轻量应用服务器适合中小企业或个人开发者搭建企业官网、博客论坛、微信小程序或开发测试环境&#xff0c;云服务器CV…

全网最详细的本地搭建GitLab代码仓库教学

大体的步骤 本地安装VMware虚拟机。然后再虚拟机上安装CentOs7镜像系统。在Linux中安装GitLab-Ce。在Linux中安装GitLab-Runner在自己搭建的GitLab上面创建一个项目&#xff0c;然后拉取到本地然后提交之后实现自动化部署。 步骤一 这一步骤我在之前我的安装Redis文章中有讲解…

Redis到底是什么?都有哪些特性?看完这一篇就都会了

目录 Redis是什么 Redis的应用场景有哪些&#xff1f; Redis的数据类型及主要特性 Redis的数据结构 简单动态字符串SDS (Simple Dynamic String) SDS的特点 Redis特性1&#xff1a;事务 Redis特性2&#xff1a;发布订阅(Pub/Sub) Redis特性3&#xff1a;Stream Redis…