SpringCloud系列:聊聊SpringCloud 中的父子容器
概述
在引入SpringCloud 的项目中会多次创建Spring 容器,本篇从源码角度深入分析具体哪些点会创建Spring 容器,以及这些容器之间的区别与联系。
本文介绍的Spring相关容器是基于SpringCloud Finchley.RELEASE 版本。

容器大致分为三层,分别对应上面的三类:
· BootStrap上下文:由SpringCloud 监听器创建,用来初始化SpringCloud 上下文,也是祖先容器。
· SpringBoot 上下文:由SpringBoot创建,也是项目中常用的Spring容器。
· 微服务配置上下文:Feign和Ribbon配置类对应的上下文,由配置容器抽象工厂 NamedContextFactory 创建,用于容器隔离。
分别来看。
BootStrap 上下文
在之前的博客《SpringBoot | 第一篇:启动流程源码分析(上)》中,提到了SpringBoot 在启动时,会触发相关一系列监听器,监听器各司其职,做一些初始化预处理操作。SpringCloud 实现了自己的监听器:BootstrapApplicationListener,来初始化SpringCloud上下文环境。
来看一下该监听器被触发后的处理逻辑:
@OverridepublicvoidonApplicationEvent(ApplicationEnvironmentPreparedEventevent) {ConfigurableEnvironment environment =event.getEnvironment();//如果未开启SpringCloud,直接返回if(!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,true)) {return;}// don't listen to events in a bootstrap context//判断该监听器是否已经执行过,如果执行过,直接返回if(environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {return;}//这里返回了一个Spring容器ConfigurableApplicationContext context = bootstrapServiceContext(environment,event.getSpringApplication());apply(context,event.getSpringApplication(), environment);}
bootstrapServiceContext方法创建了一个Spring 容器:
private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment,finalSpringApplication application) {StandardEnvironment bootstrapEnvironment =newStandardEnvironment();MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();for(PropertySource<?> source : bootstrapProperties) {bootstrapProperties.remove(source.getName());}//设置读取bootstrap文件StringconfigName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");//设置bootstrap文件路径StringconfigLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");Map<String,Object> bootstrapMap =newHashMap<>();bootstrapMap.put("spring.config.name", configName);if(StringUtils.hasText(configLocation)) {bootstrapMap.put("spring.config.location", configLocation);}//设置是否已经初始化BootStrap环境bootstrapProperties.addFirst(newMapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));for(PropertySource<?> source : environment.getPropertySources()) {bootstrapProperties.addLast(source);}//......//加载BootstrapConfiguration配置类List<String> names = SpringFactoriesLoader.loadFactoryNames(BootstrapConfiguration.class, classLoader);for(Stringname : StringUtils.commaDelimitedListToStringArray(environment.getProperty("spring.cloud.bootstrap.sources",""))) {names.add(name);}//创建Spring容器SpringApplicationBuilder builder =newSpringApplicationBuilder().profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF).environment(bootstrapEnvironment).properties("spring.application.name:"+ configName).registerShutdownHook(false).logStartupInfo(false).web(false);List<Class<?>> sources =newArrayList<>();builder.sources(sources.toArray(newClass[sources.size()]));AnnotationAwareOrderComparator.sort(sources);finalConfigurableApplicationContext context = builder.run();//创建祖先容器addAncestorInitializer(application, context);bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);returncontext;}
首先,SpringBoot项目是通过SpringApplicationBuilder启动,在上述逻辑中又构建了一个SpringApplicationBuilder对象,再次执行run方法,所以启动流程会执行两遍,只是读取的配置文件和配置类不同。
之前有人问我,SpringCloud项目ApplicationContextInitializer实现类中的逻辑执行了两遍,原因就在于启动流程会执行两遍。
同样,当第二次创建SpringApplicationBuilder并启动时,会不会再次出发监听器,然后接着创建SpringApplicationBuilder呢?
肯定不会。否则就是死循环了。上面已经提到了,SpringCloud通过标识符BOOTSTRAP_PROPERTY_SOURCE_NAME来判断。监听器执行之后,会设置该变量对应值,下次启动前如果有值,表明已经执行。
上面有一行关键的代码:addAncestorInitializer(application, context);
ancestor中文祖先的意思。具体来看一下:
privatevoidaddAncestorInitializer(SpringApplication application,ConfigurableApplicationContext context){booleaninstalled =false;//遍历所有的initializer,判断是否已经存在祖先initializerfor(ApplicationContextInitializer<?> initializer : application.getInitializers()) {if(initializerinstanceofAncestorInitializer) {installed =true;//如果存在,则设置bootStrapApplication((AncestorInitializer) initializer).setParent(context);}}//如果不存在,则创建。if(!installed) {application.addInitializers(newAncestorInitializer(context));}}
这里主要是创建 AncestorInitializer对象。
当BootStrap环境初始化完毕后,再次回到SpringBoot初始化流程会触发所有的initializers,当执行AncestorInitializer时,将BootStrap ApplicationContext容器设为父容器:
privatestaticclassAncestorInitializerimplementsApplicationContextInitializer<ConfigurableApplicationContext>,Ordered{privateConfigurableApplicationContext parent;publicAncestorInitializer(ConfigurableApplicationContext parent){this.parent = parent;}@Overridepublicvoidinitialize(ConfigurableApplicationContext context){//如果已经存在父容器,则直接取出while(context.getParent() !=null&& context.getParent() != context) {context = (ConfigurableApplicationContext) context.getParent();}reorderSources(context.getEnvironment());//设置父容器newParentContextApplicationContextInitializer(this.parent).initialize(context);}}
上述方法将设置父容器的逻辑委托给ParentContextApplicationContextInitializer类处理,来看下initialize方法:
publicclassParentContextApplicationContextInitializerimplementsApplicationContextInitializer<ConfigurableApplicationContext>,Ordered{privateintorder = Ordered.HIGHEST_PRECEDENCE;privatefinalApplicationContext parent;@Overridepublicvoidinitialize(ConfigurableApplicationContext applicationContext){if(applicationContext !=this.parent) {//设置父容器applicationContext.setParent(this.parent);//创建监听器,主要用来发布项目中存在父子容器事件applicationContext.addApplicationListener(EventPublisher.INSTANCE);}}}
BootStrap Application 容器的作用:
提前加载SpringCloud 相关的配置类,比如BootStrap Application会提前加载配置中心相关配置类,优先加读取bootstrap配置文件等逻辑。
默认加载的配置如下:

SpringBoot上下文
SpringBoot创建的Spring容器是最核心的容器,也是使用最多的Spring容器。
创建的对象会有3种类型,Servlet,Reactive,和默认。
在SpringBoot2.x版本中的判断如下:
publicclassSpringApplication{//......protectedConfigurableApplicationContextcreateApplicationContext() {Class<?> contextClass =this.applicationContextClass;if(contextClass ==null) {try{switch(this.webApplicationType) {caseSERVLET:contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);break;caseREACTIVE:contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);break;default:contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);}}catch(ClassNotFoundException ex) {thrownewIllegalStateException("Unable create a default ApplicationContext, "+"please specify an ApplicationContextClass",ex);}}return(ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);}//......}
具体细节不多介绍了,创建流程可参考之前SpringBoot启动流程源码分析文章。
微服务配置容器
上面uml 图中提到了一个关键类:NamedContextFactory,从命名可以看出,这是一个工厂类:抽象容器工厂。同hystrix 线程隔离原理一样,该工厂根据不同的服务名称,创建不同的容器。该容器有2个实现类,FeignContext 和 SpringClientFactory,分别用来加载对应的配置。

来看一下相关的核心代码:
publicabstractclassNamedContextFactory<C extends NamedContextFactory.Specification>implements DisposableBean, ApplicationContextAware {//Feign和Ribbon配置抽象接口publicinterfaceSpecification{String getName();Class<?>[] getConfiguration();}//Application集合privateMap<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();protectedAnnotationConfigApplicationContext getContext(String name) {//根据服务名称获取对应配置工厂,如果没有,则创建if(!this.contexts.containsKey(name)) {synchronized (this.contexts) {if(!this.contexts.containsKey(name)) {//创建并进行缓存this.contexts.put(name, createContext(name));}}}returnthis.contexts.get(name);}protectedAnnotationConfigApplicationContext createContext(String name) {//创建一个Spring容器AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();if(this.configurations.containsKey(name)) {for(Class<?> configuration :this.configurations.get(name).getConfiguration()) {//注入配置类context.register(configuration);}}//注入默认的Feign或Ribbon配置类for(Map.Entry<String, C> entry :this.configurations.entrySet()) {if(entry.getKey().startsWith("default.")) {for(Class<?> configuration : entry.getValue().getConfiguration()) {context.register(configuration);}}}context.register(PropertyPlaceholderAutoConfiguration.class,this.defaultConfigType);context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,Collections.<String, Object> singletonMap(this.propertyName, name)));if(this.parent !=null) {// Uses Environment from parent as well as beans//设置父类为SpringBoot创建的Spring容器context.setParent(this.parent);}//启动容器context.refresh();returncontext;}}
具体执行细节这里不做展开了,之前的文章《SpringCloud | SpringCloud Feign的前世今生【源码深入分析】》有详细介绍。
所以,具体Feign 和Ribbon配置类会创建多少实例,和项目本身依赖发服务有关。如果依赖10个服务,那就是10个Feign配置容器+10个Ribbon配置容器+SpringBoot容器+BootStrap容器。
可以借助工具来看一下。如果项目引入了SpringBoot 监控模块Spring Boot Actuator,那在idea中可以看到已经创建的8个容器如下:

注意:由于Ribbon 默认会采用懒加载,也就是只有第一次请求的时候才会加载。所以idea这里不会显示Ribbon 相关配置类容器,只显示项目启动流程中创建完成的Spring 容器。这也是微服务经常第一次调用超时的根本原因,因为创建并启动一个Spring容器需要一定的时间。
总结
本篇主要介绍了SpringCloud 项目中创建的Spring 容器:
首先SpringBoot 项目启动,触发监听器,如果引入了SpringCloud 中的BootstrapApplicationListener,则开始初始化SpringCloud 相关的上下文:Bootstrap ApplicationContext,将其设置为祖先容器,然后继续创建其子容器:SpringBoot ApplicationContext。
如果引入了FeignClient,则会实例化一个容器工厂,以服务名称为key,value为Feign 和Ribbon配置类容器,配置隔离,父容器则都为SpringBoot ApplicationContext。
原创作者:方志朋
方志朋简介:SpringCloud中国社区联合创始人,博客访问量突破一千万,爱好开源,热爱分享,活跃于各大社区,保持着非常强的学习驱动力,终身学习践行者,终身学习受益者。目前就职于国内某家知名互联网保险公司,担任DEVOPS工程师,对微服务领域和持续集成领域研究较深,精通微服务框架SpringCloud
- 点赞
- 收藏
- 关注作者
评论(0)