首页 > 其他分享 >Springcloud环境中bootstrap.yml加载原理

Springcloud环境中bootstrap.yml加载原理

时间:2023-02-19 23:23:51浏览次数:39  
标签:String Springcloud bootstrap springframework environment context org yml

如果是Springcloud 项目,一般会将配置中心、注册中心等地址存入bootstrap.yml 中,同时将其他的配置存入配置中心。

也就是说bootstrap.yml 的读取会比较靠前。 下面研究其机制。

1. Springcloud 启动

​ Springcloud 环境启动过个中有一个重要的listener。BootstrapApplicationListener. 这个listener 在Spring-cloud-context-xxx.jar 中,且在spring.factories 文件自动配置。

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
  1. 查看org.springframework.cloud.bootstrap.BootstrapApplicationListener#onApplicationEvent 调用时机

将断点下到org.springframework.cloud.bootstrap.BootstrapApplicationListener#onApplicationEvent 方法,查看其调用时机如下:

核心过程如下:

(1).org.springframework.boot.SpringApplication#prepareEnvironment boot启动过程中准备环境

(2).org.springframework.boot.context.event.EventPublishingRunListener#environmentPrepared 广播环境准备事件

(3).org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType) 获取到所有的ApplicationListener, 开始广播

(4) 调用到org.springframework.cloud.bootstrap.BootstrapApplicationListener#onApplicationEvent

  1. org.springframework.cloud.bootstrap.BootstrapApplicationListener#onApplicationEvent 调用
    准备一系列的Sprinigcloud 环境,注册一些PropertySource, 处理SpringApplication、environment 对象等。

核心代码如下:org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext

	private ConfigurableApplicationContext bootstrapServiceContext(
			ConfigurableEnvironment environment, final SpringApplication application,
			String configName) {
    // 重置environment
		StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
		MutablePropertySources bootstrapProperties = bootstrapEnvironment
				.getPropertySources();
		for (PropertySource<?> source : bootstrapProperties) {
			bootstrapProperties.remove(source.getName());
		}
		String configLocation = environment
				.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
		Map<String, Object> bootstrapMap = new HashMap<>();
		bootstrapMap.put("spring.config.name", configName);
		// if an app (or test) uses spring.main.web-application-type=reactive, bootstrap
		// will fail
		// force the environment to use none, because if though it is set below in the
		// builder
		// the environment overrides it
		bootstrapMap.put("spring.main.web-application-type", "none");
		if (StringUtils.hasText(configLocation)) {
			bootstrapMap.put("spring.config.location", configLocation);
		}
    // 添加一个boostrap的MapPropertySource,后面解析bootstrap.yml 会用到
		bootstrapProperties.addFirst(
				new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
		for (PropertySource<?> source : environment.getPropertySources()) {
			if (source instanceof StubPropertySource) {
				continue;
			}
			bootstrapProperties.addLast(source);
		}
		
    // 重建一个builder
		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();
		if (builderApplication.getMainApplicationClass() == null) {
			// gh_425:
			// SpringApplication cannot deduce the MainApplicationClass here
			// if it is booted from SpringBootServletInitializer due to the
			// absense of the "main" method in stackTraces.
			// But luckily this method's second parameter "application" here
			// carries the real MainApplicationClass which has been explicitly
			// set by SpringBootServletInitializer itself already.
			builder.main(application.getMainApplicationClass());
		}
		if (environment.getPropertySources().contains("refreshArgs")) {
			// If we are doing a context refresh, really we only want to refresh the
			// Environment, and there are some toxic listeners (like the
			// LoggingApplicationListener) that affect global static state, so we need a
			// way to switch those off.
			builderApplication
					.setListeners(filterListeners(builderApplication.getListeners()));
		}
		builder.sources(BootstrapImportSelectorConfiguration.class);
    
    // builder.run() 相当于会重亲启动ApplicationContext (这一步会启动Springboot环境,加载配置也是在这一步骤进行加载的)
		final ConfigurableApplicationContext context = builder.run();
		// gh-214 using spring.application.name=bootstrap to set the context id via
		// `ContextIdApplicationContextInitializer` prevents apps from getting the actual
		// spring.application.name
		// during the bootstrap phase.
		context.setId("bootstrap");
		// Make the bootstrap context a parent of the app context
		addAncestorInitializer(application, context);
		// It only has properties in it now that we don't want in the parent so remove
		// it (and it will be added back later)
		bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
    // 合并propertySource
		mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
    // 返回Context,相当于替换Context
		return context;
	}

简单理解过程如下:springboot启动-》准备环境-》springcloudlistener操作(1. 设置springcloud环境-》2.builder.run启动springcloud环境(大体类似boot启动)-》3.清理环境中springcloud相关信息-》合并environment,重置context)-》boot其他listener-》boot其他启动逻辑

这里return 之前的environment 如下,如图:

2. bootstrap.yml加载过程

断点查看Springcloud 环境完全启动的environment的相关source :

过程解释:

​ 上面说了org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext 方法builder.run(); 会使用上面springcloud 初始化之后的一些环境进行启动,相当于原来springboot环境的启动。 启动完成之后替换Springboot的context和environment 对象。

  • builder.run(); 重新启动
  1. 调用到:org.springframework.boot.SpringApplication#run(java.lang.String...)

  2. 然后继续调用到:org.springframework.boot.SpringApplication#prepareEnvironment 方法

  3. org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent

    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        List<EnvironmentPostProcessor> postProcessors = this.loadPostProcessors();
        postProcessors.add(this);
        AnnotationAwareOrderComparator.sort(postProcessors);
        Iterator var3 = postProcessors.iterator();

        while(var3.hasNext()) {
            EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var3.next();
            postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
        }

    }

获取所有的环境后置处理器,进行后置处理。这里包括:

  1. 继续调用到org.springframework.boot.context.config.ConfigFileApplicationListener

这个里面加载重要的配置信息并且进行加载配置。

    private static final String DEFAULT_PROPERTIES = "defaultProperties";
    private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
    private static final String DEFAULT_NAMES = "application";
    private static final Set<String> NO_SEARCH_NAMES = Collections.singleton((Object)null);
    private static final Bindable<String[]> STRING_ARRAY = Bindable.of(String[].class);
    private static final Bindable<List<String>> STRING_LIST = Bindable.listOf(String.class);
    private static final Set<String> LOAD_FILTERED_PROPERTY;
    public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
    public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";
    public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
    public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";
    public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
    public static final int DEFAULT_ORDER = -2147483638;      

        private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
            this.getSearchLocations().forEach((location) -> {
                boolean isFolder = location.endsWith("/");
                Set<String> names = isFolder ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
                names.forEach((name) -> {
                    this.load(location, name, profile, filterFactory, consumer);
                });
            });
        }

        private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
            if (!StringUtils.hasText(name)) {
                Iterator var13 = this.propertySourceLoaders.iterator();

                PropertySourceLoader loader;
                do {
                    if (!var13.hasNext()) {
                        throw new IllegalStateException("File extension of config file location '" + location + "' is not known to any PropertySourceLoader. If the location is meant to reference a directory, it must end in '/'");
                    }

                    loader = (PropertySourceLoader)var13.next();
                } while(!this.canLoadFileExtension(loader, location));

                this.load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
            } else {
                Set<String> processed = new HashSet();
                Iterator var7 = this.propertySourceLoaders.iterator();

                while(var7.hasNext()) {
                    PropertySourceLoader loaderx = (PropertySourceLoader)var7.next();
                    String[] var9 = loaderx.getFileExtensions();
                    int var10 = var9.length;

                    for(int var11 = 0; var11 < var10; ++var11) {
                        String fileExtension = var9[var11];
                        if (processed.add(fileExtension)) {
                            this.loadForFileExtension(loaderx, location + name, "." + fileExtension, profile, filterFactory, consumer);
                        }
                    }
                }

            }
        }

        private Set<String> getSearchNames() {
            if (this.environment.containsProperty("spring.config.name")) {
                String property = this.environment.getProperty("spring.config.name");
                return this.asResolvedSet(property, (String)null);
            } else {
                return this.asResolvedSet(ConfigFileApplicationListener.this.names, "application");
            }
        }

org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load传入的参数如下:name = bootstrap, location=classpath:/

​ 这里的核心逻辑是

(1). 获取搜索目录searchLocations,成员变量设置的默认值;

(2). 获取搜索的name; 如果包含spring.config.name ( 就获取cloud启动设置的bootstrap);如果不包含就获取application.xxx (这里也就是为什么bootstrap.yml 优先获取)。

​ 当springcloud 环境启动完成builder.run()执行完之后, 有一个remove bootstrap 的操作。 所以springcloud 环境启动完成之后相当于删除掉标记,然后合并context和environment 之后继续springboot 的启动。

(3). 然后遍历当前的loaders(也就是property和yml加载器),然后获取当前loader 支持的后缀(property),最后拼接传递参数让loader 去依次到指定位置获取。支持的后缀如下:(简单理解就是调用方给一个文件的basename和location,比如bootstrap,这里会分别调用两个loader 两次,然后传递不同的后缀去指定路径找文件加载。这也是为什么properties\yml\yaml都支持的原因。)

org.springframework.boot.env.PropertiesPropertySourceLoader#getFileExtensions
    public String[] getFileExtensions() {
        return new String[]{"properties", "xml"};
    }

org.springframework.boot.env.YamlPropertySourceLoader#getFileExtensions
    public String[] getFileExtensions() {
        return new String[]{"yml", "yaml"};
    }
  1. 继续调用到:org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#loadDocuments

  2. 继续调用到: org.springframework.boot.env.YamlPropertySourceLoader#load 解析classpath:./bootstrap.yml

  3. 创建一个OriginTrackedMapPropertySource然后加入到

        public OriginTrackedMapPropertySource(String name, Map source, boolean immutable) {
            super(name, source);
            this.immutable = immutable;
        }
    

    这里传入的参数如下:

这里就完成了加载bootstrap.yaml。

可能不通的Springboot 加载的类以及后置处理器不一样,但大体思路都是这样。

springoot 启动-》environment准备-》广播事件-》获取EnvironmentPostProcessor 环境后置处理器-》执行org.springframework.boot.env.EnvironmentPostProcessor#postProcessEnvironment 方法进行初始化配置加载(可能不同的版本采用不通的后置处理器处理)。

标签:String,Springcloud,bootstrap,springframework,environment,context,org,yml
From: https://www.cnblogs.com/qlqwjy/p/17135908.html

相关文章

  • Bootstrap框架
    导入:为什么要使用BootStrapBootstrap,来自Twitter,是一款受欢迎的前端框架。Bootstrap是基于HTML、CSS、JAVASCRIPT的,它简洁灵活,使得Web开发更加快捷。大家可以在......
  • AndroidApex技术分析调研1-apexd-bootstrap执行流程分析
    分析代码基线android10-releaseAPEX:AndroidPonyEXpress安卓运行环境AndroidRuntime(ART)将会在安卓12中,添加到ProjectMainline当中,这意味着可以通过GooglePlay商店......
  • CICD流水线 Jenkins + Docker compose 分环境 一键部署SpringCloud项目
    一、环境准备接上篇:上篇搭建好了Jenkins 环境 并把docker-compose.yml Dockerfile 相关jar包推送到了目标服务器。二、分环境部署1、SpringBoot配置pom.xml<pro......
  • Springboot项目把配置文件application.properties改成application.yml,以及多环境配置
    1、Springboot的配置文件application.properties直接改成application.yml,然后按照yml格式配置应该就能生效了如果不能生效,可以看一下target里面的配置文件是properties还......
  • springcloud day01
    单体架构:业务所有功能都在一个项目中开发,打成一个包部署优点是架构简单部署成本低缺点是耦合度高分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为一个独立的......
  • SpringCloud Alibaba框架都有啥啊
    前言springcloud是一个基于springboot实现的微服务架构开发工具,使用springcloud框架进行微服务业务开发是java后端开发必备技能,目前主流的SpringCloud分为SpringCloudNet......
  • 什么是bootstrap?
    Incomputing,theterm bootstrap meanstobootortoloadaprogramintoacomputerusingamuchsmallerinitialprogramtoloadinthedesiredprogram,whic......
  • SpringCloudStarterSleuth搭建&使用
    是什么SpringCloudSleuth是SpringCloud生态系统中的一个分布式跟踪解决方案,可以用于跟踪微服务应用程序中的请求链路。它通过在请求中添加唯一标识符(TraceID)和调用标......
  • SpringCloudGateway启动报错解决
    报错启动项目后报错,提示如下2023-02-1621:51:35.786ERROR[main]o.s.boot.diagnostics.LoggingFailureAnalysisReporter.report:40-***************************APP......
  • 引入spring-cloud-starter-bootstrap但不建bootstrap文件,导致nacos config 报错.longP
    使用SpringCloud添加了一个新服务,上线报错:[ERROR]2023-02-1612:40:29785[com.alibaba.nacos.client.config.http.ServerHttpAgent][com.alibaba.nacos.client.Worke......