聊聊SpringCloud环境下父子容器

聊聊SpringCloud环境下父子容器
注: 本文基于 SpringBoot-2.0.4-RELEASE S pringCloud-Finchley.SR1 版本.
起因

​ Debug Spring web环境时了解到在启动期间创建了两个容器: Spring IOC容器和Web容器,其中Web容器是Spring IOC 的子容器。Debug SpringBoot源码时了解到 SpringBoot启动期间只创建了一个Web容器。今天在学习Feign源码时Debug到FeignContext时突然看到FeignContext的父容器是SpringBoot创建的Web容器,打开Web容器一看发现Web容器也有一个父容器: BootStrap Context。突然乱了阵脚,难道SpringBoot在启动期间创建了两个容器?带着这个问题又Debug了下SpringBoot的源码,便有了今天这篇文章,Mark一下。

探索

​ 带着上面遇到的疑惑,我分别在SpringCloud环境和SpringBoot环境下分别对SpringBoot启动流程Debug了一遍。功夫不负有心人,两个环境一对比变发现了其中的奥妙。在SpringBoot环境下Debug并没有发现什么异常(差点以为是之前研究SpringBoot时偷懒了呢),当在SpringCloud 环境下发现了与SpringBoot环境的差异。

SpringBoot通过Main方法进行启动:

该启动过程分为两步:第一步:包装SpringApplication; 第二步:使用包装好的SpringApplication调用run()进行启动。

public static void main(Stirng[] args) {
    SpringApplication.run(TestApplication.class, args);
}
包装SpringApplication

在包装SpringApplication这步中发现了一处差异:

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

​ SpringBoot环境 : 获取到12个ApplicationListener

​ SpringCloud环境: 获取到13个ApplicationListener

其中多出来的一个监听器为 : BootstrapApplicationListener,在IDEA全文中搜索该监听器发现它在 spring-cloud-context包中。既然找到了差异,那么接着往下看哪里使用了这边扫描出来的 ApplicationListener.

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = deduceWebApplicationType();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
       //通过Spring SPI方式获取所有的 ApplicationListener
		setListeners((Collection)            getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

通过Debug 发现,在run方法中调用的 prepareEnvironment()方法的listeners.environmentPrepared(environment); 中获取到l BootStrapApplicationListener.

我们重点看看这个BootStrapApplicationListener到底做了什么:

@Override
	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        //获取到当前的环境变量
		ConfigurableEnvironment environment = event.getEnvironment();
        //如果为开启SpringCloud直接返回
		if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
				true)) {
			return;
		}
		//判断该监听器是否已经执行过,如果执行过会直接返回
		if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
			return;
		}
        
		ConfigurableApplicationContext context = null;
        //设置读取bootstrap文件
		String configName = environment
				.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
		for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
				.getInitializers()) {
			if (initializer instanceof ParentContextApplicationContextInitializer) {
				context = findBootstrapContext(
						(ParentContextApplicationContextInitializer) initializer,
						configName);
			}
		}
        //创建一个新的Spring容器
		if (context == null) {
			context = bootstrapServiceContext(environment, event.getSpringApplication(),
					configName);
		}
		apply(context, event.getSpringApplication(), environment);
	}

bootstrapServiceContext方法创建了一个新的Context:

private ConfigurableApplicationContext bootstrapServiceContext(
			ConfigurableEnvironment environment, final SpringApplication application,
			String configName) {
		StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
		MutablePropertySources bootstrapProperties = bootstrapEnvironment
				.getPropertySources();
		for (PropertySource<?> source : bootstrapProperties) {
			bootstrapProperties.remove(source.getName());
		}
        //设置 bootstrap 文件路径
		String configLocation = environment
				.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
		Map<String, Object> bootstrapMap = new HashMap<>();
		bootstrapMap.put("spring.config.name", configName);
	
		bootstrapMap.put("spring.main.web-application-type", "none");
		if (StringUtils.hasText(configLocation)) {
			bootstrapMap.put("spring.config.location", configLocation);
		}
    
        //设置是否已经初始化bootstrap环境
		bootstrapProperties.addFirst(
				new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
		for (PropertySource<?> source : environment.getPropertySources()) {
			if (source instanceof StubPropertySource) {
				continue;
			}
			bootstrapProperties.addLast(source);
		}
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		
		List<String> names = new ArrayList<>(SpringFactoriesLoader
				.loadFactoryNames(BootstrapConfiguration.class, classLoader));
		for (String name : StringUtils.commaDelimitedListToStringArray(
				environment.getProperty("spring.cloud.bootstrap.sources", ""))) {
			names.add(name);
		}
		
        //使用SpringApplicationBuilder手动创建一个新的容器
		SpringApplicationBuilder builder = new SpringApplicationBuilder()
				.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
				.environment(bootstrapEnvironment)
				.registerShutdownHook(false).logStartupInfo(false)
				.web(WebApplicationType.NONE);
    
	    //省略部分代码。。。。
    
        //手动调用 SpringApplication.run()方法实例化新创建的容器
		final ConfigurableApplicationContext context = builder.run();
		
		context.setId("bootstrap");
		// 创建祖先容器
		addAncestorInitializer(application, context);
		bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
		mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
		return context;
	}
private void addAncestorInitializer(SpringApplication application,
			ConfigurableApplicationContext context) {
		boolean installed = false;
      //遍历所有的initializer,判断是否已经存在 祖先initializer
		for (ApplicationContextInitializer<?> initializer : application
				.getInitializers()) {
			if (initializer instanceof AncestorInitializer) {
				installed = true;
				//如果存在,则设置 bootStrapApplication
				((AncestorInitializer) initializer).setParent(context);
			}
		}
        //如果不存在,则创建。
		if (!installed) {
			application.addInitializers(new AncestorInitializer(context));
		}

}

这里主要是创建AncestorInitializer对象。

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

private static class AncestorInitializer implements
            ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

        private ConfigurableApplicationContext parent;

        public AncestorInitializer(ConfigurableApplicationContext parent) {
            this.parent = parent;
        }
        @Override
        public void initialize(ConfigurableApplicationContext context) {
            //如果已经存在父容器,则直接取出
            while (context.getParent() != null && context.getParent() != context) {
                context = (ConfigurableApplicationContext) context.getParent();
            }
            reorderSources(context.getEnvironment());
            //执行这一步:设置父容器
            new ParentContextApplicationContextInitializer(this.parent)
                    .initialize(context);
        }
}

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

public class ParentContextApplicationContextInitializer implements
        ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

    private int order = Ordered.HIGHEST_PRECEDENCE;

    private final ApplicationContext parent;

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        if (applicationContext != this.parent) {
            //将当前手动创建的BootStrap容器设置为父容器
            applicationContext.setParent(this.parent);
            //创建监听器,主要用来发布项目中存在父子容器事件
            applicationContext.addApplicationListener(EventPublisher.INSTANCE);
        }
    }
}

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

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

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

总结

​ 在SpringBoot环境只创建了一个WEB容器,在SpringCloud环境下创建了两个容器: Bootstrap父容器和Web子容器。所以在SpringCloud环境下容器结构是这样的:
在这里插入图片描述
需要注意的是SpringCloud环境下会执行两次 SpringApplication的run()方法。如果要对SpringBoot进行扩展的话需要保持自定义逻辑是支持幂等的。


流程如下:

 1.	Main方法启动SpringBoot
 2.	调用SpringApplication.run()方法
 3.	扫描到 BootStrapApplicationListener 
 4.	调用BootStrapApplicationListener#onApplicationEvent()
 5.	判断enviroment中是否可以取到 “bootstrap” 属性,若有则直接return返回
 6.	向enviroment塞入 “bootstrap” 标识已经执行过该监听器 
 7.	手动创建新的SpringApplication()并调用run方法
 8.	 扫描到 BootStrapApplicationListener
 9.	调用BootStrapApplicationListener#onApplicationEvent() 
 10.	enviroment可以取到 “bootstrap” 标识则判断已经执行过了该监听器,直接return
 11.	执行第二次run()方法后续流程完成Bootstrap容器创建
 12.	设置BootStrap容器为祖先容器
 13.	回到第3步走第一次run()方法扫描到 BootStrapApplicationListener的地方执行后续流程
 14.	完成WEB容器启动
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值