SpringCloud父子容器源码浅析

一. 前言

1. 相关文章

2. 说明

  • 本文SpringCloud源码解读使用的版本为2.2.7.RELEASE. 引入的依赖为 spring-cloud-context:2.2.7.RELEASE
  • 本文涉及的SpringBoot源码版本为2.3.9.RELEASE. 引入依赖 spring-boot-starter-web:2.3.9.RELEASE
  • 附上SpringCloud官方文档中文版地址: https://www.springcloud.cc/spring-reference.html
  • 附上SpringCloud官方文档地址: https://spring.p2hp.com/cloud.html

二. 源码浅析

1. 启动流程

在这里插入图片描述

在这里插入图片描述
简易流程
在这里插入图片描述

2. 进入源码

2.1 父容器创建入口

SpringApplication#prepareEnvironment, 准备环境完成后, 发布ApplicationEnvironmentPreparedEvent事件

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		//...
		// 准备环境完成, 发布ApplicationEnvironmentPreparedEvent事件
		listeners.environmentPrepared(environment);
		//...
	}

2.2 BootstrapApplicationListener监听事件处理

BootstrapApplicationListener这个监听器是在spring-cloud-context的META-INF.spring.factories中添加的. 在SpringApplication实例化时该bean就会添加到应用监听器列表中去

在这里插入图片描述
进入BootstrapApplicationListener#onApplicationEvent

    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        // 是否开启bootstrap父容器创建, 默认都是开启的
        if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) {
            return;
        }
        // 判断enviroment中是否有"bootstrap"配置源. 有该配置源则表示正在创建父容器, 直接结束避免死循环
        if (environment.getPropertySources().contains("bootstrap")) {
            return;
        }
        ConfigurableApplicationContext context = null;
        // 通过ParentContextApplicationContextInitializer获取父容器.
        String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
        for (ApplicationContextInitializer<?> initializer : event.getSpringApplication().getInitializers()) {
            if (initializer instanceof ParentContextApplicationContextInitializer) {
            	// 获取ParentContextApplicationContextInitializer中存储的父容器
                // 注: 正常启动时, debug发现这里永远不会执行, 因为ParentContextApplicationContextInitializer是手动new出来没有添加到容器中.
                //     源码位置BootstrapApplicationListener.AncestorInitializer#initialize
                context = findBootstrapContext((ParentContextApplicationContextInitializer) initializer, configName);
            }
        }

        if (context == null) {
            // 创建父容器, 添加祖先监听器, 为Environment添加属性源
            context = bootstrapServiceContext(environment, event.getSpringApplication(), configName);
            // 为子SpringApplication添加一个应用监听器CloseContextOnFailureApplicationListener, 监听启动失败事件
            event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
        }

        // 为子SpringApplication添加主加载源和添加应用上下文监听器
        apply(context, event.getSpringApplication(), environment);
    }

2.3 创建父容器(核心代码)

核心的代码在BootstrapApplicationListener#bootstrapServiceContext中. 下面展开看下它做了什么

    private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment,
            final SpringApplication application, String configName) {
        // 创建父容器的Environment(实现类为StandardEnvironment), 清空所有属性源PropertySource
        StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
        MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
        for (PropertySource<?> source : bootstrapProperties) {
            bootstrapProperties.remove(source.getName());
        }

        // 构建一个名为bootstrap的属性源, 添加到父容器的Environment
        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);
        bootstrapMap.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);
        }
        bootstrapProperties.addFirst(new MapPropertySource("bootstrap", bootstrapMap));

        // 父Environment添加 子Environment中非StubPropertySource的属性源
        for (PropertySource<?> source : environment.getPropertySources()) {
            if (source instanceof StubPropertySource) {
                continue;
            }
            bootstrapProperties.addLast(source);
        }

        // 构建父SpringApplication
        SpringApplicationBuilder builder = new SpringApplicationBuilder()
                .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
                .environment(bootstrapEnvironment)
                .registerShutdownHook(false).logStartupInfo(false)
                .web(WebApplicationType.NONE);
        final SpringApplication builderApplication = builder.application();

        // 正常启动不执行代码块. main方法启动类是SpringApplication创建时可以从堆栈中获取到的
        if (builderApplication.getMainApplicationClass() == null) {
            builder.main(application.getMainApplicationClass());
        }
        // 正常启动不执行代码块. 若子environment含有refreshArgs属性源则过滤掉日志相关的监听器
        if (environment.getPropertySources().contains("refreshArgs")) {
            builderApplication.setListeners(filterListeners(builderApplication.getListeners()));
        }

        // 添加加载源, 在build()时会将该加载源作为父SpringApplication唯一的primarySources
        builder.sources(BootstrapImportSelectorConfiguration.class);
        // 执行父SpringApplication的run方法, 获取到父容器ConfigurableApplicationContext
        final ConfigurableApplicationContext context = builder.run();

        // 父容器命名为bootstrap
        context.setId("bootstrap");
        // 给子SpringApplication添加一个祖先初始化器AncestorInitializer.
        // AncestorInitializer作用: 暂存了父容器对象, 待后续创建出子容器后进行父子容器绑定
        addAncestorInitializer(application, context);

        // 移除父容器environment中的bootstrap的属性源
        bootstrapProperties.remove("bootstrap");
        // 为父子environment都添加同一个名为springCloudDefaultProperties空的属性源
        mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
        return context;
    }

这里实际上就做了以下几件事

  • 创建父Environment和父SpringApplication, 执行run方法后创建出父容器
  • 为子SpringApplication添加祖先监听器, 以便后续创建出子容器后 完成父子容器绑定
  • 为父子SpringApplication添加一个公共的名为springCloudDefaultProperties空的属性源

这里提到了一个祖先监听器AncestorInitializer, 我们简单看下它的代码

    private static class AncestorInitializer implements
            ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
        
        private ConfigurableApplicationContext parent;
        AncestorInitializer(ConfigurableApplicationContext parent) {
            this.parent = parent;
        }

        //...

        public void initialize(ConfigurableApplicationContext context) {
            while (context.getParent() != null && context.getParent() != context) {
                context = (ConfigurableApplicationContext) context.getParent();
            }
            // 重排子Environment中springCloudDefaultProperties属性源顺序
            reorderSources(context.getEnvironment());
            // 绑定父子容器关系. 等价于context.setParent(this.parent)
            new ParentContextApplicationContextInitializer(this.parent)
                    .initialize(context);
        }
        //...
    }

	// ParentContextApplicationContextInitializer#initialize代码如下
	private final ApplicationContext parent;
	public void initialize(ConfigurableApplicationContext applicationContext) {
		if (applicationContext != this.parent) {
			// 绑定父子容器
			applicationContext.setParent(this.parent);
			// 为子容器添加EventPublisher监听器
			applicationContext.addApplicationListener(EventPublisher.INSTANCE);
		}
	}

AncestorInitializer监听器是加到子SpringApplication中, 当子容器创建完成调用SpringApplication#prepareContext方法时, 就会执行到该祖先初始化器的initialize方法, 最终执行到ParentContextApplicationContextInitializer#initialize, 完成父子容器的绑定

注: 这里的ParentContextApplicationContextInitializer仅仅是为了调用初始化方法, 并没有将该初始化器的bean放入容器中

2.4 执行BootstrapApplicationListener#apply

为子SpringApplication添加主加载源和初始化器

    private void apply(ConfigurableApplicationContext context,
                       SpringApplication application, ConfigurableEnvironment environment) {
        if (application.getAllSources().contains(BootstrapMarkerConfiguration.class)) {
            return;
        }
        // 为子SpringApplication添加一个主加载源BootstrapMarkerConfiguration
        application.addPrimarySources(Arrays.asList(BootstrapMarkerConfiguration.class));

        // 为子SpringApplication添加所有类型为ApplicationContextInitializer的初始化器
        Set target = new LinkedHashSet<>(application.getInitializers());
        target.addAll(getOrderedBeansOfType(context, ApplicationContextInitializer.class));
        application.setInitializers(target);
        
        // 为子SpringApplication添加初始化器DelegatingEnvironmentDecryptApplicationInitializer
        addBootstrapDecryptInitializer(application);
    }


    private void addBootstrapDecryptInitializer(SpringApplication application) {
        DelegatingEnvironmentDecryptApplicationInitializer decrypter = null;
        Set<ApplicationContextInitializer<?>> initializers = new LinkedHashSet<>();
        for (ApplicationContextInitializer<?> ini : application.getInitializers()) {
            if (ini instanceof EnvironmentDecryptApplicationInitializer) {
                ApplicationContextInitializer del = ini;
                decrypter = new DelegatingEnvironmentDecryptApplicationInitializer(del);
                initializers.add(ini);
                // 若子SpringApplication含有EnvironmentDecryptApplicationInitializer初始化器,
                // 则再添加一个对应的DelegatingEnvironmentDecryptApplicationInitializer
                initializers.add(decrypter);
            } else if (ini instanceof DelegatingEnvironmentDecryptApplicationInitializer) {
                // do nothing
            } else {
                initializers.add(ini);
            }
        }
        ArrayList<ApplicationContextInitializer<?>> target = new ArrayList<ApplicationContextInitializer<?>>(
                initializers);
        application.setInitializers(target);
    }

三. 拓展

暂无

四. 思考

1. 为什么SpringCloud使用父子容器这种模式? 父子容器的作用是什么?

以下仅为个人理解

  • SpringBoot创建的容器(IOC容器)不适合SpringCloud框架代码的执行.
  • SpringCloud容器和SpringBoot容器相互隔离, 以免父容器启动时修改到子容器的关键属性. 例如两者所需的Environment和PropertySource是不一样的, SpringCloud加载配置文件和SpringBoot也不完全相同
  • SpringCloud和SpringBoot启动所需条件是不一样的. 例如SpringCloud以非servlet和非reactive的应用类型启动.

2. 使用nacos配置中心时, 若启动完成后, 去更改配置文件, 会重新触发Spring启动, 那此时父子容器创建和启动时有什么不同呢?

五. 参考文档

  • 聊聊SpringCloud环境下父子容器: https://blog.csdn.net/jackcheng1117/article/details/105844597
  • 23
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值