springboot原理分析(二)

文章详细分析了SpringEnvironment的加载过程,包括activeProfiles的定义和合并规则,以及配置文件如application.yml的加载顺序。特别提到了SpringBoot中StandardServletEnvironment的初始化,以及系统属性和环境变量的加载顺序。ConfigFileApplicationListener在ApplicationEnvironmentPreparedEvent事件中的作用,如何处理配置文件并影响propertySource列表。此外,还讨论了bootstrap过程,解释了为何配置加载可能发生两次,并介绍了bootstrap.yml在SpringCloud中的角色和用途。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

spring environment加载过程分析

activeProfiles

activeProfiles在AbstractEnvironment里定义,是一个LinkedHashSet,其内容来自于:

spring.profiles.active + spring.profiles.include

其顺序则依赖于插入顺序(LinkedHashSet自身的特点)。

注意:activeProfiles是所有配置文件指定的spring.profiles.active 和 spring.profiles.include项的合集,并不像其他选项那样是先后覆盖关系!

所以,如果有特别依赖插入顺序的场合,要注意spring.profiles.include里的profile的顺序。

application.yml文件优先级说明

spring boot下application-xx.yml里的配置会覆盖application.yml,测一下即可知道。

环境加载过程分析

创建基本配置

一开始,会根据web应用的类型,创建基本的环境:

private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        } else {
            switch(this.webApplicationType) {
            case SERVLET:
                return new StandardServletEnvironment();
            case REACTIVE:
                return new StandardReactiveWebEnvironment();
            default:
                return new StandardEnvironment();
            }
        }
    }

对于我们的springboot应用来说,就是StandardServletEnvironment。它自带的propertySource列表如下:

0 = {PropertySource$StubPropertySource@3839} "StubPropertySource {name='servletConfigInitParams'}"
1 = {PropertySource$StubPropertySource@3840} "StubPropertySource {name='servletContextInitParams'}"
2 = {PropertiesPropertySource@3841} "PropertiesPropertySource {name='systemProperties'}"
3 = {SystemEnvironmentPropertySource@3842} "SystemEnvironmentPropertySource {name='systemEnvironment'}"

其中,SystemProperties是java虚拟机启动参数,SystemEnvironment是操作系统环境变量(比如我们经常配的JAVA_HOME,就在其中)。

注意propertySource列表里,排最前面的优先级最高

加载application-XX.yml配置

接着,会发送ApplicationEnvironmentPreparedEvent 事件,经过ApplicationListener处理后,环境的propertySource列表如下:

0 = {ConfigurationPropertySourcesPropertySource@5596} "ConfigurationPropertySourcesPropertySource {name='configurationProperties'}"
1 = {PropertySource$StubPropertySource@5597} "StubPropertySource {name='servletConfigInitParams'}"
2 = {PropertySource$StubPropertySource@5598} "StubPropertySource {name='servletContextInitParams'}"
3 = {PropertiesPropertySource@5599} "PropertiesPropertySource {name='systemProperties'}"
4 = {SystemEnvironmentPropertySourceEnvironmentPostProcessor$OriginAwareSystemEnvironmentPropertySource@5600} "OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}"
5 = {RandomValuePropertySource@5601} "RandomValuePropertySource {name='random'}"
6 = {BootstrapApplicationListener$ExtendedDefaultPropertySource@5602} "ExtendedDefaultPropertySource {name='springCloudDefaultProperties'}"
7 = {MapPropertySource@5603} "MapPropertySource {name='springCloudClientHostInfo'}"
...
11 = {OriginTrackedMapPropertySource@5607} "OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application-local.yml]'}"
12 = {OriginTrackedMapPropertySource@5608} "OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.yml]'}"
13 = {MapPropertySource@5609} "MapPropertySource {name='defaultProperties'}"

configurationProperties不用多看,它只是引用了全部的propertySource列表。

springCloudClientHostInfo持有的是本地主机的机器名和ip地址。

springCloudDefaultProperties里是用于spring cloud的配置,一般没配置bootstrap.yml就没有。

springCloudClientHostInfo、application-local.yml、application.yml都是由ConfigFileApplicationListener把它们加入到环境里的。

ConfigFileApplicationListener实现原理

来看看ConfigFileApplicationListener响应ApplicationEnvironmentPreparedEvent 事件的处理:

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());
        }

    }

可见,它依赖可定制的EnvironmentPostProcessor,每个processor要实现postProcessEnvironment()接口。我们的框架下有9个:

0 = {SystemEnvironmentPropertySourceEnvironmentPostProcessor@3949} 
1 = {SpringApplicationJsonEnvironmentPostProcessor@3950} 
2 = {HostInfoEnvironmentPostProcessor@3951} 
3 = {EnvirmentConfigListener@3952} 
4 = {CloudFoundryVcapEnvironmentPostProcessor@3953} 
5 = {ConfigFileApplicationListener@3917} 
6 = {ApolloApplicationContextInitializer@3954} 
7 = {TraceEnvironmentPostProcessor@3955} 
8 = {DebugAgentEnvironmentPostProcessor@3956} 

注意:其中的ConfigFileApplicationListener就是它自己,因为ConfigFileApplicationListener自己也实现了EnvironmentPostProcessor接口,它就是在这个接口里加载了application-XX.yml,参见“ConfigFileApplicationListener默认动作分析”一节。

HostInfoEnvironmentPostProcessor生成了springCloudClientHostInfo配置:

public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        HostInfo hostInfo = this.getFirstNonLoopbackHostInfo(environment);
        LinkedHashMap<String, Object> map = new LinkedHashMap();
        //找到机器名和ip地址,生成springCloudClientHostInfo配置
        map.put("spring.cloud.client.hostname", hostInfo.getHostname());
        map.put("spring.cloud.client.ip-address", hostInfo.getIpAddress());
        MapPropertySource propertySource = new MapPropertySource("springCloudClientHostInfo", map);
        environment.getPropertySources().addLast(propertySource);
    }

这个类定义在spring-cloud-commons包里。

TraceEnvironmentPostProcessor生成了defaultProperties配置,是为sleuth准备的:

logging.pattern.level -> %5p [${spring.zipkin.service.name:${spring.application.name:}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]

spring-boot环境配置加载了两次的问题

从debug过程来看,foundation框架执行了两次SpringApplication.run,第二次run()后的environment如下:

0 = {MapPropertySource@15650} "MapPropertySource {name='server.ports'}"
1 = {EncryptableEnumerablePropertySourceWrapper@15651} "EncryptableEnumerablePropertySourceWrapper {name='ApolloPropertySources'}"
2 = {ConfigurationPropertySourcesPropertySource$$EnhancerBySpringCGLIB$$e8d7ddec@15652} "ConfigurationPropertySourcesPropertySource {name='configurationProperties'}"
3 = {EncryptablePropertySourceWrapper@15653} "EncryptablePropertySourceWrapper {name='servletConfigInitParams'}"
4 = {EncryptablePropertySourceWrapper@15654} "EncryptablePropertySourceWrapper {name='servletContextInitParams'}"
5 = {EncryptableMapPropertySourceWrapper@15655} "EncryptableMapPropertySourceWrapper {name='systemProperties'}"
6 = {EncryptableMapPropertySourceWrapper@15656} "EncryptableMapPropertySourceWrapper {name='systemEnvironment'}"
7 = {EncryptablePropertySourceWrapper@15657} "EncryptablePropertySourceWrapper {name='random'}"
8 = {EncryptableMapPropertySourceWrapper@15658} "EncryptableMapPropertySourceWrapper {name='springCloudClientHostInfo'}"
...
12 = {EncryptableMapPropertySourceWrapper@15662} "EncryptableMapPropertySourceWrapper {name='applicationConfig: [classpath:/application-local.yml]'}"
13 = {EncryptableMapPropertySourceWrapper@15663} "EncryptableMapPropertySourceWrapper {name='applicationConfig: [classpath:/application.yml]'}"
14 = {EncryptableMapPropertySourceWrapper@15664} "EncryptableMapPropertySourceWrapper {name='springCloudDefaultProperties'}"
15 = {EncryptablePropertySourceWrapper@15665} "EncryptablePropertySourceWrapper {name='cachedrandom'}"
16 = {EncryptableMapPropertySourceWrapper@15666} "EncryptableMapPropertySourceWrapper {name='defaultProperties'}"

这里,ApolloPropertySources在第二次的refreshContext阶段加了进来,且优先级最高。这也能解释,为何apollo的配置优先级最高,优先级接下来依次是:java命令行参数、环境变量、application-xx.yml、application.yml。

环境配置加载了两次,跟BootstrapApplicationListener有关,也就是说,在prepareEnvironment()阶段,就有一个临时的springApplication执行了一次完整的run过程,产生了一个父ApplicationContext,我们称之为bootstrap context。第二次springApplication.run才是主ApplicationContext的创建,这个主ApplicationContext也就是应用最终看到的ApplicationContext。

我们来看代码:

public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        // spring.cloud.bootstrap.enabled默认为true,也可以禁掉
        if ((Boolean)environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) {
            //几乎所有的ApplicationListener里都有这样的判断,确保两次加载的情况下,不会多次执行本listener的逻辑
            if (!environment.getPropertySources().contains("bootstrap")) {
                ConfigurableApplicationContext context = null;
                ......

                if (context == null) {
                    //通过临时的springApplication来构建bootstrap context,这里的configName为bootstrap
                    context = this.bootstrapServiceContext(environment, event.getSpringApplication(), configName);
                    //这个是为了在整个springApplication失败时能关闭bootstrap ApplicationContext
                    event.getSpringApplication().addListeners(new ApplicationListener[]{new BootstrapApplicationListener.CloseContextOnFailureApplicationListener(context)});
                }
                //在主SpringApplication里做一个BootstrapMarkerConfiguration标记,表明bootstrap过程已经做完,后续不用反复做了。同时,补充bootstrap过程引入的新的ApplicationContextInitializer到主SpringApplication中
                this.apply(context, event.getSpringApplication(), environment);
            }
        }
    }

private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment, final SpringApplication application, String configName) {
        //先新建一个StandardEnvironment的基本环境,一开始有:java命令行参数+环境变量等2个默认propertySource
        StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
        MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
        Iterator var6 = bootstrapProperties.iterator();
		
        //清空bootstrapEnvironment里的propertySource
        while(var6.hasNext()) {
            PropertySource<?> source = (PropertySource)var6.next();
            bootstrapProperties.remove(source.getName());
        }

        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);
        }
        //往bootstrapEnvironment里添加bootstrap propertySource
        bootstrapProperties.addFirst(new MapPropertySource("bootstrap", bootstrapMap));
    
        //environment是主applicationContext里的环境配置
        Iterator var9 = environment.getPropertySources().iterator();
        
        //将主环境里除了servletConfigInitParams和servletContextInitParams之外的propertySource都加到bootstrap 环境里,此时,bootstrap 环境里的propertySource列表是:bootstrap、configurationProperties、systemProperties、systemEnvironment
        while(var9.hasNext()) {
            PropertySource<?> source = (PropertySource)var9.next();
            if (!(source instanceof StubPropertySource)) {
                bootstrapProperties.addLast(source);
            }
        }

        //用bootstrap 环境来构建一个临时的SpringApplication
        SpringApplicationBuilder builder = (new SpringApplicationBuilder(new Class[0])).profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF).environment(bootstrapEnvironment).registerShutdownHook(false).logStartupInfo(false).web(WebApplicationType.NONE);
        SpringApplication builderApplication = builder.application();
        ......
        //用于构建bootstrap过程的导入配置,导入的是spring.factories文件中的org.springframework.cloud.bootstrap.BootstrapConfiguration指定的配置类,这些配置类将在bootstrap过程的refreshContext阶段里处理掉。
        builder.sources(new Class[]{BootstrapImportSelectorConfiguration.class});
        //这里运行临时springApplication,构建出bootstrap Context
        ConfigurableApplicationContext context = builder.run(new String[0]);
        context.setId("bootstrap");
        this.addAncestorInitializer(application, context);
        bootstrapProperties.remove("bootstrap");
       //将springCloudDefaultProperties 同时merge到application和bootstrap环境里,springCloudDefaultProperties里有可能会包含bootstrap.yml配置文件,用于spring cloud的启动配置
        this.mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
        return context;
    }

总的来讲,bootstrap过程就是为了引入BootstrapConfiguration配置类,并完成bootstrap.yml文件的加载(spring cloud下如果有的话),其所创建的bootstrap Context与主ApplicationContext其实没啥关系。

bootstrap阶段的作用:

  • 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
  • 一些固定的不能被覆盖的属性(bootstrap.yml里的配置不能被application.yml覆盖)
  • 一些加密/解密的场景
SpringBoot的运行原理可以分为以下几个方面: 1️⃣ 父依赖:SpringBoot项目的pom.xml文件中会引入一个父依赖,这个父依赖中包含了一些常用的依赖插件,简化了项目的配置构建过程。 2️⃣ starter场景启动器:SpringBoot提供了一系列的starter依赖,每个starter都包含了一组相关的依赖配置,可以方便地引入配置需要的功能。 3️⃣ 主启动类:SpringBoot项目的主启动类使用@SpringBootApplication注解进行标注,这个注解表示这是一个Spring Boot应用。在主启动类中,通过调用SpringApplication.run方法来启动Spring Boot应用。 4️⃣ spring.factories:Spring Boot使用spring.factories文件来配置自动装配的类。这个文件中定义了一些自动配置类的全限定名,当应用启动时,Spring Boot会根据这些配置来自动装配相应的功能。 5️⃣ SpringApplication.run分析SpringApplication.run方法是Spring Boot应用的入口,它会创建一个SpringApplication实例,并根据配置来启动Spring Boot应用。在启动过程中,会加载配置文件、创建Spring容器、执行自动装配等操作。 总结起来,SpringBoot原理可以概括为通过父依赖、starter场景启动器、主启动类、spring.factoriesSpringApplication.run方法来简化自动化Spring应用的配置启动过程。\[1\] \[2\] \[3\] #### 引用[.reference_title] - *1* *2* [SpringBoot运行原理分析](https://blog.csdn.net/gaowenhui2008/article/details/130456549)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Springboot工作原理详解](https://blog.csdn.net/huangtenglong/article/details/127862112)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值