当前位置:   article > 正文

聊聊 SpringCloud 中的父子容器

springcloud bootstrap容器作用

来源公号:吉姆餐厅ak

 

概述

在引入 SpringCloud 的项目中会多次创建 Spring 容器,本篇从源码角度深入分析具体哪些点会创建 Spring 容器,以及这些容器之间的区别与联系。

本文介绍的Spring相关容器是基于 SpringCloud Finchley.RELEASE 版本。


 

640?wx_fmt=png

 

容器大致分为三层,分别对应上面的三类:

  • BootStrap上下文:由SpringCloud 监听器创建,用来初始化 SpringCloud 上下文,也是祖先容器。

  • SpringBoot 上下文:由SpringBoot创建,也是项目中常用的Spring容器。

  • 微服务配置上下文:Feign和Ribbon配置类对应的上下文,由配置容器抽象工厂 NamedContextFactory 创建,用于容器隔离。

分别来看。


BootStrap 上下文

在之前的博客《SpringBoot | 第一篇:启动流程源码分析(上)》中,提到了 SpringBoot 在启动时,会触发相关一系列监听器,监听器各司其职,做一些初始化预处理操作。SpringCloud 实现了自己的监听器:BootstrapApplicationListener,来初始化SpringCloud上下文环境。

来看一下该监听器被触发后的处理逻辑:

  1. @Override
  2.     public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
  3.         ConfigurableEnvironment environment = event.getEnvironment();
  4.         //如果未开启SpringCloud,直接返回
  5.         if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
  6.                 true)) {
  7.             return;
  8.         }
  9.         // don't listen to events in a bootstrap context
  10.         //判断该监听器是否已经执行过,如果执行过,直接返回
  11.         if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
  12.             return;
  13.         }
  14.         //这里返回了一个 Spring 容器
  15.         ConfigurableApplicationContext context = bootstrapServiceContext(environment,
  16.                 event.getSpringApplication());
  17.         apply(context, event.getSpringApplication(), environment);
  18.     }

bootstrapServiceContext方法创建了一个 Spring 容器:

  1. private ConfigurableApplicationContext bootstrapServiceContext(
  2.             ConfigurableEnvironment environmentfinal SpringApplication application) {
  3.         StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
  4.         MutablePropertySources bootstrapProperties = bootstrapEnvironment
  5.                 .getPropertySources();
  6.         for (PropertySource<?> source : bootstrapProperties) {
  7.             bootstrapProperties.remove(source.getName());
  8.         }
  9.         //设置读取 bootstrap 文件
  10.         String configName = environment
  11.                 .resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
  12.         //设置 bootstrap 文件路径
  13.         String configLocation = environment
  14.                 .resolvePlaceholders("${spring.cloud.bootstrap.location:}");
  15.         Map<StringObject> bootstrapMap = new HashMap<>();
  16.         bootstrapMap.put("spring.config.name", configName);
  17.         if (StringUtils.hasText(configLocation)) {
  18.             bootstrapMap.put("spring.config.location", configLocation);
  19.         }
  20.         //设置是否已经初始化BootStrap环境
  21.         bootstrapProperties.addFirst(
  22.                 new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
  23.         for (PropertySource<?> source : environment.getPropertySources()) {
  24.             bootstrapProperties.addLast(source);
  25.         }
  26.             //......
  27.             //加载BootstrapConfiguration 配置类
  28.             List<String> names = SpringFactoriesLoader
  29.                 .loadFactoryNames(BootstrapConfiguration.class, classLoader);
  30.             for (String name : StringUtils.commaDelimitedListToStringArray(
  31.                     environment.getProperty("spring.cloud.bootstrap.sources"""))) {
  32.                 names.add(name);
  33.             }
  34.         //创建 Spring 容器
  35.         SpringApplicationBuilder builder = new SpringApplicationBuilder()
  36.                 .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
  37.                 .environment(bootstrapEnvironment)
  38.                 .properties("spring.application.name:" + configName)
  39.                 .registerShutdownHook(false)
  40.                 .logStartupInfo(false)
  41.                 .web(false);
  42.         List<Class<?>> sources = new ArrayList<>();
  43.         builder.sources(sources.toArray(new Class[sources.size()]));
  44.         AnnotationAwareOrderComparator.sort(sources);
  45.         final ConfigurableApplicationContext context = builder.run();
  46.         //创建祖先容器
  47.         addAncestorInitializer(application, context);
  48.         bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
  49.         mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
  50.         return context;
  51.     }

首先,SpringBoot项目是通过SpringApplicationBuilder启动,在上述逻辑中又构建了一个SpringApplicationBuilder对象,再次执行run方法,所以启动流程会执行两遍,只是读取的配置文件和配置类不同。

之前有人问我,SpringCloud项目ApplicationContextInitializer实现类中的逻辑执行了两遍,原因就在于启动流程会执行两遍。

同样,当第二次创建SpringApplicationBuilder并启动时,会不会再次出发监听器,然后接着创建SpringApplicationBuilder呢?
肯定不会。否则就是死循环了。上面已经提到了,SpringCloud通过标识符BOOTSTRAP_PROPERTY_SOURCE_NAME来判断。监听器执行之后,会设置该变量对应值,下次启动前如果有值,表明已经执行。

上面有一行关键的代码:addAncestorInitializer(application, context);

ancestor 中文祖先的意思。具体来看一下:

  1. private void addAncestorInitializer(SpringApplication application,
  2.             ConfigurableApplicationContext context) {
  3.         boolean installed = false;
  4.         //遍历所有的initializer,判断是否已经存在 祖先initializer
  5.         for (ApplicationContextInitializer<?> initializer : application
  6.                 .getInitializers()) {
  7.             if (initializer instanceof AncestorInitializer) {
  8.                 installed = true;
  9.                 // 如果存在,则设置 bootStrapApplication
  10.                 ((AncestorInitializer) initializer).setParent(context);
  11.             }
  12.         }
  13.         //如果不存在,则创建。
  14.         if (!installed) {
  15.             application.addInitializers(new AncestorInitializer(context));
  16.         }
  17.     }

这里主要是创建 AncestorInitializer对象。

BootStrap环境初始化完毕后,再次回到SpringBoot初始化流程会触发所有的initializers,当执行AncestorInitializer时,将BootStrap ApplicationContext容器设为父容器:

  1. private static class AncestorInitializer implements
  2.             ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
  3.         private ConfigurableApplicationContext parent;
  4.         public AncestorInitializer(ConfigurableApplicationContext parent) {
  5.             this.parent = parent;
  6.         }
  7.         @Override
  8.         public void initialize(ConfigurableApplicationContext context) {
  9.             //如果已经存在父容器,则直接取出
  10.             while (context.getParent() != null && context.getParent() != context) {
  11.                 context = (ConfigurableApplicationContext) context.getParent();
  12.             }
  13.             reorderSources(context.getEnvironment());
  14.             //设置父容器
  15.             new ParentContextApplicationContextInitializer(this.parent)
  16.                     .initialize(context);
  17.         }
  18. }

上述方法将设置父容器的逻辑委托给ParentContextApplicationContextInitializer类处理,来看下initialize方法:

  1. public class ParentContextApplicationContextInitializer implements
  2.         ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
  3.     private int order = Ordered.HIGHEST_PRECEDENCE;
  4.     private final ApplicationContext parent;
  5.     @Override
  6.     public void initialize(ConfigurableApplicationContext applicationContext) {
  7.         if (applicationContext != this.parent) {
  8.             //设置父容器
  9.             applicationContext.setParent(this.parent);
  10.             //创建监听器,主要用来发布项目中存在父子容器事件
  11.             applicationContext.addApplicationListener(EventPublisher.INSTANCE);
  12.         }
  13.     }
  14. }

BootStrap Application 容器的作用:
提前加载SpringCloud 相关的配置类,比如BootStrap Application会提前加载配置中心相关配置类,优先加读取bootstrap配置文件等逻辑。

默认加载的配置如下:

 

640?wx_fmt=png

 


SpringBoot上下文

SpringBoot创建的 Spring容器是最核心的容器,也是使用最多的Spring容器。
创建的对象会有3种类型,Servlet,Reactive,和默认。
在SpringBoot2.x版本中的判断如下:

  1. public class SpringApplication {
  2.     //......
  3.     protected ConfigurableApplicationContext createApplicationContext() {
  4.         Class<?> contextClass = this.applicationContextClass;
  5.         if (contextClass == null) {
  6.             try {
  7.                 switch (this.webApplicationType) {
  8.                 case SERVLET:
  9.                     contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
  10.                     break;
  11.                 case REACTIVE:
  12.                     contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
  13.                     break;
  14.                 default:
  15.                     contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
  16.                 }
  17.             }
  18.             catch (ClassNotFoundException ex) {
  19.                 throw new IllegalStateException(
  20.                         "Unable create a default ApplicationContext, "
  21.                                 + "please specify an ApplicationContextClass",
  22.                         ex);
  23.             }
  24.         }
  25.         return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
  26.     }
  27.     //......
  28. }

具体细节不多介绍了,创建流程可参考之前 SpringBoot启动流程源码分析文章。


微服务配置容器

上面 uml 图中提到了一个关键类:NamedContextFactory,从命名可以看出,这是一个工厂类:抽象容器工厂。同 hystrix 线程隔离原理一样,该工厂根据不同的服务名称,创建不同的容器。该容器有2个实现类,FeignContext 和 SpringClientFactory,分别用来加载对应的配置。

 

640?wx_fmt=png

 


来看一下相关的核心代码:

  1. public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
  2.         implements DisposableBean, ApplicationContextAware {
  3.     //Feign 和Ribbon 配置抽象接口
  4.     public interface Specification {
  5.         String getName();
  6.         Class<?>[] getConfiguration();
  7.     }
  8.     //Application集合
  9.     private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
  10.     protected AnnotationConfigApplicationContext getContext(String name) {
  11.             //根据服务名称获取对应配置工厂,如果没有,则创建
  12.             if (!this.contexts.containsKey(name)) {
  13.                 synchronized (this.contexts) {
  14.                     if (!this.contexts.containsKey(name)) {
  15.                         //创建并进行缓存
  16.                         this.contexts.put(name, createContext(name));
  17.                     }
  18.                 }
  19.             }
  20.             return this.contexts.get(name);
  21.         }
  22.     protected AnnotationConfigApplicationContext createContext(String name) {
  23.         //创建一个 Spring 容器
  24.         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
  25.         if (this.configurations.containsKey(name)) {
  26.             for (Class<?> configuration : this.configurations.get(name)
  27.                     .getConfiguration()) {
  28.                 //注入配置类
  29.                 context.register(configuration);
  30.             }
  31.         }
  32.         //注入默认的Feign或Ribbon配置类
  33.         for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
  34.             if (entry.getKey().startsWith("default.")) {
  35.                 for (Class<?> configuration : entry.getValue().getConfiguration()) {
  36.                     context.register(configuration);
  37.                 }
  38.             }
  39.         }
  40.         context.register(PropertyPlaceholderAutoConfiguration.class,
  41.                 this.defaultConfigType);
  42.         context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
  43.                 this.propertySourceName,
  44.                 Collections.<StringObject> singletonMap(this.propertyName, name)));
  45.         if (this.parent != null) {
  46.             // Uses Environment from parent as well as beans
  47.             //设置父类为 SpringBoot 创建的Spring 容器
  48.             context.setParent(this.parent);
  49.         }
  50.         //启动容器
  51.         context.refresh();
  52.         return context;
  53.     }
  54. }

具体执行细节这里不做展开了,之前的文章《SpringCloud | SpringCloud Feign的前世今生【源码深入分析】》有详细介绍。

所以,具体Feign 和 Ribbon配置类会创建多少实例,和项目本身依赖发服务有关。如果依赖10个服务,那就是10个Feign配置容器+10个Ribbon配置容器+SpringBoot容器+BootStrap容器。

可以借助工具来看一下。如果项目引入了SpringBoot 监控模块Spring Boot Actuator,那在idea中可以看到已经创建的8个容器如下:

 

640?wx_fmt=png

 

注意:由于Ribbon 默认会采用懒加载,也就是只有第一次请求的时候才会加载。所以idea这里不会显示 Ribbon 相关配置类容器,只显示项目启动流程中创建完成的 Spring 容器。 这也是微服务经常第一次调用超时的根本原因,因为创建并启动一个Spring容器需要一定的时间。


总结

本篇主要介绍了 SpringCloud 项目中创建的 Spring 容器:

首先 SpringBoot 项目启动,触发监听器,如果引入了SpringCloud 中的BootstrapApplicationListener,则开始初始化 SpringCloud 相关的上下文:Bootstrap ApplicationContext,将其设置为祖先容器,然后继续创建其子容器:SpringBoot ApplicationContext。

如果引入了 FeignClient,则会实例化一个容器工厂,以服务名称为key,value为Feign 和 Ribbon配置类容器,配置隔离,父容器则都为 SpringBoot ApplicationContext。

-更多文章-

刚出炉的一套面试题(JAVA岗)

这 10 款插件让你的 GitHub 更好用、更有趣

Nginx是什么 ? 能干嘛 ?

MAT入门到精通(二)

MAT入门到精通(一)

为了效率,扎克伯格的26张PPT

分布式架构知识体系

Spring Cloud Consul 之Greenwich版本全攻略

 

-关注我-

640?wx_fmt=png

看完了,帮我点个“好看”鸭

点鸭点鸭

↓↓↓↓

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/人工智能uu/article/detail/786823
推荐阅读
相关标签
  

闽ICP备14008679号