1.前言
接触过Spring Cloud都知道,服务启动的时候会先启动Spring Cloud容器加载bootstrap.yml的配置,然后再启动我们常说的Spring容器,那么为什么需要父子容器,父容器又是在什么地方进行创建的呢?
2. 为什么需要父子容器?
这个问题暂时没看到官方的答案,但可以根据父子容器的目的来进行推论一下。
- 隔离
Spring Cloud目的是为了解决微服务之间通信时候的一系列问题,它本质并不与具体的业务有关系,所以将它设置为父容器,我们可以将环境独立,不与具体业务环境(Spring容器)进行耦合。 - 层级划分
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应用的环境和配置。