• Spring Cloud之BootstrapApplicationListener巧妙构建双容器


    在看eureka server源码的时候发现spring的refresh方法被执行了两次!开始还以为是自己代码写错了,经过调试发现spring cloud竟然自己启动了一个单独的容器;下面我们来分析一下代码原理

    BootstrapApplicationListener引入时机

    接上一篇讲解【springboot事件管理机制】文章 https://blog.csdn.net/Aqu415/article/details/126197702

    spring cloud框架提供了的BootstrapApplicationListener会在SpringApplication的构造方法里被解析并缓存进入容器
    在这里插入图片描述

    类图如下:

    在这里插入图片描述

    onApplicationEvent 方法分析

    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    		ConfigurableEnvironment environment = event.getEnvironment();
    		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;
    		}
    		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) {
    // @A
    			context = bootstrapServiceContext(environment, event.getSpringApplication(),
    					configName);
    			event.getSpringApplication()
    					.addListeners(new CloseContextOnFailureApplicationListener(context));
    		}
    
    		apply(context, event.getSpringApplication(), environment);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    @A:spring cloud 另起炉灶,启动新的spring容器,确切的说是SpringApplication

    bootstrapServiceContext方法
    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());
    		}
    		String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
    		String configAdditionalLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
    		
    		... 省略...
    		
    		// @A
    		SpringApplicationBuilder builder = new SpringApplicationBuilder()
    				.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
    				.environment(bootstrapEnvironment)
    				// Don't use the default properties in this builder
    				.registerShutdownHook(false).logStartupInfo(false)
    				.web(WebApplicationType.NONE);
    		final SpringApplication builderApplication = builder.application();
    		
    		... 省略...
    		// @B	
    		builder.sources(BootstrapImportSelectorConfiguration.class);
    		final ConfigurableApplicationContext context = builder.run();
            
            // @C
    		addAncestorInitializer(application, context);
    
            ... 省略...
    		bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
    		mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
    		return context;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    @A:构建一个SpringApplicationBuilder对象
    @B:设置springapplication的sources属性【偷天换日】,比较精妙;下面会分析
    @C:将spring cloud的容器和main方法启动的容器设置父子关系

    	private void addAncestorInitializer(SpringApplication application,
    			ConfigurableApplicationContext context) {
    		boolean installed = false;
    		
    ... 省略...
    
    		if (!installed) {
    // @A
    			application.addInitializers(new AncestorInitializer(context));
    		}
    
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    @A:向application的Initializer里放了一个AncestorInitializer对象,application即springboot原生的应用对象;在外层主流程的prepareContext会调用所有的Initializer的initialize方法
    在这里插入图片描述
    图中
    1、@A是spring cloud的BootstrapApplicationListener执行时机
    2、@B是所有Initializer执行时机

    在这里插入图片描述
    initialize 方法

    		@Override
    		public void initialize(ConfigurableApplicationContext context) {
    			while (context.getParent() != null && context.getParent() != context) {
    				context = (ConfigurableApplicationContext) context.getParent();
    			}
    			reorderSources(context.getEnvironment());
    // @A
    			new ParentContextApplicationContextInitializer(this.parent)
    					.initialize(context);
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    @A:设置springcloud容器和标准springboot容器的父子关系

    BootstrapImportSelectorConfiguration

    上面讲了springcloudi借助BootstrapApplicationListener将BootstrapImportSelectorConfiguration注入到容器中;
    使用的如下代码:

    builder.sources(BootstrapImportSelectorConfiguration.class);
    
    • 1

    todo~~

  • 相关阅读:
    看十年架构师用Spring Boot 整合 MongoDB 实战解说
    qt5.12.9编译32位的qtwebengin模块
    设计模式系列详解 -- 迭代器模式
    小分子化合物在重编程中的应用
    Java 字节流写数据加异常处理之finally的用法
    详细解释 React 组件的生命周期方法
    阶段性总结与思考
    [MQ] MQ的应用场景及Docker安装RabbitMQ
    指针学习总结
    Unity技术手册 - 创建物体有几种方式?
  • 原文地址:https://blog.csdn.net/Aqu415/article/details/126206360