springboot创建并配置环境3 - 配置扩展属性(下)

简介: springboot创建并配置环境3 - 配置扩展属性(下)

一、介绍

上一篇文章:springboot如何创建并配置环境3 - 配置扩展属性(上) 中我们介绍了springboot对配置文件的处理逻辑,但是由于篇幅过长,决定分上下集两部分讲解。

本文基于以下版本进行展开:

  • jdk:1.8
  • springboot:2.4.3

另外:由于篇幅过长,决定分四集文章来讲解分析

一、创建环境

二、配置基础环境

三、配置扩展属性(上)

四、配置扩展属性(下)

二、对contributors中的配置属性进行处理

上集对processAndApply()方法的分析中,概括来讲就是分四步:①在确定profiles前处理contributors中的配置属性。②确定profiles。③在确定profiles后处理contributors中的配置属性。④将contributors中的配置属性应用到当前运行环境Environment中。

因此下面我们来分析以下逻辑

  • 处理contributors中的配置属性
  • 确定profiles
  • 将配置属性应用到当前运行环境Environment中。

三、处理contributors中的配置属性

该逻辑通过三个方法完成,分别是processInitial()processWithoutProfiles()processWithProfiles()。而这三个方法其实内部实现都是通过调用contributors对象的withProcessedImports()方法完成的,他们之间的区别就是是否传入确定的profiles

处理contributors对象中的配置属性.png

因此我们主要对withProcessedImports()方法进行分析。

先来看一下该方法的源码如下:

ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter importer,
            ConfigDataActivationContext activationContext) {
   
    // 根据环境激活上下文获取导入阶段,所谓导入阶段为profiles激活前和profiles激活后两个阶段
    ImportPhase importPhase = ImportPhase.get(activationContext);
    this.logger.trace(LogMessage.format("Processing imports for phase %s. %s", importPhase,
                                        (activationContext != null) ? activationContext : "no activation context"));
    // this表示contributors表示的对象,将该对象赋值给result,本质上仍然是保存contributor集合的contributors对象
    ConfigDataEnvironmentContributors result = this;
    int processed = 0;
    while (true) {
   
        // 从contributor集合中获取下一个将要处理的contributor
        ConfigDataEnvironmentContributor contributor = getNextToProcess(result, activationContext, importPhase);
        if (contributor == null) {
   
            // 如果contributor集合中没有要处理的元素,则返回该集合
            this.logger.trace(LogMessage.format("Processed imports for of %d contributors", processed));
            return result;
        }
        if (contributor.getKind() == Kind.UNBOUND_IMPORT) {
   
            // 如果contributor的类型为UNBOUND_IMPORT(未绑定导入)

            // 从当前正处理的contributor对象中获取ConfigurationPropertySource
            Iterable<ConfigurationPropertySource> sources = Collections
                .singleton(contributor.getConfigurationPropertySource());
            // 创建placeholder解析器,用来解析${}
            PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
                result, activationContext, true);
            // 创建binder对象,binder中包含了ConfigurationPropertySource解析器
            Binder binder = new Binder(sources, placeholdersResolver, null, null, null);
            // 将当前contributor复制给一个新的对象,并将类型修改为BOUND_IMPORT(已绑定导入)
            ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder);
            // 根据原contributor集合重新创建一个Contributors对象,并将当前正处理的contributor对象进行替换
            result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
                                                           result.getRoot().withReplacement(contributor, bound));
            continue;
        }
        // 创建位置解析器上下文
        ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext(
            result, contributor, activationContext);
        // 创建配置数据加载器上下文
        ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
        // 从contributor中获取imports,imports中包含了当前contributor对象要处理的配置文件路径
        List<ConfigDataLocation> imports = contributor.getImports();
        this.logger.trace(LogMessage.format("Processing imports %s", imports));
        // 调用importer的resolveAndLoad()方法来解析并读取配置数据
        // 在importer中已经包含了位置解析器、配置数据加载器。
        // 返回值是一个map对象,其中key中包含了配置文件的路径及其资源,value中包含的是配置文件中的配置数据
        Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,
                                                                                          locationResolverContext, 
                                                                                       loaderContext, 
                                                                                       imports);
        this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet())));
        // 将读取到的配置通过父子关系,设置为当前正处理的contributor的children,
        // asContributors()方法将读取到的配置封装成contributor对象并设置其类型为UNBOUND_IMPORT(未绑定导入)
        ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
                                                                                           asContributors(imported));
        // 根据原contributor集合重新创建一个Contributors对象,并将当前正处理的contributor对象进行替换
        result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
                                                       result.getRoot().withReplacement(contributor, contributorAndChildren));
        processed++;
    }
}

其中,在contributor类型为UNBOUND_IMPORT的if代码块中,主要是把该contributor的类型修改为BOUND_IMPORT,并作为一个新的contributor对象将原contributor对象进行替换。如下所示

将contributor对象的类型进行修改.png

下面,我们分析withProcessedImports()方法剩余部分逻辑:

withProcessedImports方法的主要逻辑.png

其中,我们先进入importer.resolveAndLoad()方法,该方法返回一个map对象(key为配置文件资源对象,value为从配置文件资源中加载的配置属性),其内部逻辑分两部分:①解析配置文件位置和资源,②加载配置属性

resolveAndLoad()方法.png

1. 解析配置文件位置和资源

我们进入resolve()方法查看如何解析配置文件的位置和资源。

对resolve()方法的调用.png

从上面源码中可以看到,resolve()方法对其locations参数(配置文件位置)进行遍历,对每一个配置文件位置再调用重载的resolve()方法进行解析。真正的解析过程是通过调用配置文件位置解析器resolversresolve()方法实现的。

调用配置文件位置解析器的resolve()方法.png

前面讲过,springboot提供了两个配置文件解析器:①ConfigTreeConfigDataLocationResolver;②StandardConfigDataLocationResolver。我们从这两个解析器的isResolvable()方法便可以判断出区别:前者用于解析带有前缀configtree:的配置文件路径;后者解析任意配置文件路径。

下面我们以StandardConfigDataLocationResolver为例,分析如何解析配置文件位置。

其中resolve()方法和resolveProfileSpecific()方法逻辑大致相同,只是后者携带有效的profile参数

因此我们分析其resolve()方法,该方法先获取配置文件资源的引用再根据该文件引用,获取该文件资源

获取配置文件资源的引用1.png

下面我们看如何获取配置文件资源的引用,以目录为例,查看getReferencesForDirectory()方法

获取配置文件资源的引用.png

进一步查看配置文件引用的构造方法

配置文件资源引用的构造方法.png

由此我们便可以知道,springboot是如何根据spring.profiles.active属性确定profiles对应的配置文件资源

当我们得到配置文件资源的引用后,通过该引用获取对应的配置文件资源

解析配置文件资源的引用得到文件资源.png

我们再回到配置文件位置解析器resolversresolve()方法。

将配置文件资源封装到解析结果中.png

下面我们回到resolveAndLoad()方法,其中resolved集合中包含了默认指定的以及指定profile对应的配置文件资源。然后在调用load()方法,从配置文件资源中加载配置属性即可。

resolveAndLoad()方法.png

2. 加载配置属性

这里我们关注resolveAndLoad()方法对load()方法的调用。

该方法以配置文件资源分析结果集合为参数,返回一个Map集合,其中key为配置文件资源分析结果,value为配置文件资源中的配置属性。

根据配置文件资源分析结果加载配置.png

进入加载器loadersload()方法,该方法用于加载指定配置文件资源中并返回该配置文件中的配置属性

调用配置属性加载器的load方法.png

从该方法中看到,配置属性加载器有两种,分别是ConfigTreeConfigDataLoaderStandardConfigDataLoader,还记得前面我们分析的配置文件位置解析器也有两个分别是ConfigTreeConfigDataLocationResolverStandardConfigDataLocationResolver,他们是一一对应的。

我们以StandardConfigDataLocationResolver为例,查看它的load()方法。

调用加载yaml或properties的load方法.png

从该方法中我们看到,对配置文件中配置属性的加载是通过配置属性加载器中的load()方法实现的,而该加载器又分为propertiesyaml两种。

load()方法将我们在配置文件中定义的配置属性进行加载,并转化为propertySource集合。再将该集合封装到ConfigData对象中并返回。

而该加载器中对配置文件资源中的配置属性的加载过程我们这里就不做分析了,请有兴趣的读者自查。

最后再回到withProcessedImports()方法

withProcessedImports方法的主要逻辑.png

resolveAndLoad()方法我们就分析结束了,该方法返回的imported对象为map集合,其中key为配置文件资源分析结果,value为配置文件资源中的配置属性。最后通过withChildren()方法将该map集合转为contributor对象并保存到children属性中,再通过withReplacement()方法将contributor对象更新

至此我们在配置文件中定义的所有配置属性均已保存到contributors对象中并返回。此时contributors对象的结构如下

contributors最终结构.png

四、确定当前运行环境激活的profile

此过程由processAndApply()方法中调用withProfiles()方法完成

processAndApply方法调用withProfiles.png

下面我们进入该方法源码查看

withProfiles方法.png

1. 获取附加的addtionalProfiles

从源码上看,addtionalProfiles属性是在ConfigDataEnvironment类的成员变量中直接定义的,且该属性是通过该类的构造方法设置的

class ConfigDataEnvironment {
   
    // ...
    private final Collection<String> additionalProfiles;    
    // ...

    ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
            ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles,
            ConfigDataEnvironmentUpdateListener environmentUpdateListener) {
   
        // ...
        this.additionalProfiles = additionalProfiles;
        // ...
    }
}

通过该构造方法的调用链我们可以发现,该构造方法的调用如下所示

ConfigDataEnvironment构造方法的调用链.png

由此可知,附加profiles是从SpringApplication类中获取的,那么是否也是由SpringApplication类设置的呢?答案是肯定的。

SpringApplication类中有对应的方法定义

SpringApplication设置addtionalProfiles.png

因此我们可以在springboot的主启动方法中通过以下方式设置

设置addtionalProfiles示例.png

2. 获取spring.profiles.include定义的配置

进入getIncludedProfiles()方法

获取include指定的profiles.png

从该方法中看到,对contributors中的contributor进行遍历,从中获取key为spring.profiles.include的配置属性,将其添加到集合中并返回。

3. 获取spring.profiles.active定义的配置

进入Profiles的构造方法查看,

Profiles的构造方法.png

该构造方法中定义了三种profiles,分别是spring.profiles.group定义的profiles、spring.profiles.active定义的profiles和spring.profiles.default定义的profiles。我们逐个查看

  • spring.profiles.group定义的profiles

    Profiles的构造方法得知,springboot通过spring.profiles.group定义profiles分组,且定义方式为Map集合。我们通过下面示例说明

    配置文件中定义分组profiles.png

调式源码

配置文件设置分组profiles调式结果.png

  • spring.profiles.active定义的profiles

    Profiles的构造方法得知,springboot通过getActivatedProfiles()方法获取spring.profiles.active定义的profiles

    获取active定义的profiles.png

getActivatedProfiles()方法中,springboot获取spring.profiles.active定义的profiles,并将前面获取的additionalProfiles一同添加到集合中并返回,作为Profiles实例的activeProfiles属性。

  • spring.profiles.default定义的profiles

    Profiles的构造方法得知,springboot通过getDefaultProfiles()方法获取spring.profiles.default定义的profiles

    获取default定义的profiles.png

在`getDefaultProfiles()`方法中,springboot获取`spring.profiles.default`定义的profiles(默认为`default`),并将其添加到集合中并返回,作为`Profiles`实例的`defaultProfiles`属性。

最后,在将所有定义的profiles封装到Profiles实例后,通过activationContext.withProfiles()方法将该Profiles实例添加到profiles激活上下文中。

withProfiles方法.png

五、将contributors中保存的配置信息应用到当前运行环境中

下面我们便到达processAndApply()方法的最后一步,将contributors中保存的配置信息应用到当前运行环境

processAndApply方法调用applyToEnvironment.png

下面进入applyToEnvironment方法的源码

将配置属性应用到当前运行环境中.png

从源码可见,该方法虽然较长,但逻辑比较简单,就是将contributor集合中来自配置文件的配置属性添加到当前运行环境的配置属性集合中,然后对当前运行环境设置profiles。

此时,运行环境中的所有配置属性均已设置完毕,包含来自系统的配置属性以及来自配置文件的配置属性等,如下所示

运行环境中保存的属性4.png




至此,我们对springboot创建并配置运行环境的整个过程就分析结束了,其过程虽然繁琐,但如果认真梳理,其处理逻辑并不复杂,只需我们在阅读源码时耐得住寂寞沉得住气即可。

六、总结

  • 在启动环境中主要保存配置信息和当前操作系统的配置信息以及环境变量
  • 针对不同的应用程序类型,springboot创建对应的运行环境实例,如StandardEnvironmentStandardServletEnvironmentStandardReactiveWebEnvironment
  • 在创建运行环境实例时,其构造器内部就已经首先将系统属性环境变量保存到其内部属性中了。
  • 通过观察者模式发布环境准备就绪事件,由监听该事件的各种监听器针对该事件进行不同的逻辑处理。
  • 涉及到的设计模式
    • 观察者模式:发布环境准备就绪事件,由对应的监听器执行逻辑
    • 工厂模式:环境后处理器工厂
  • 通过contributor对象临时保存所有配置文件中的配置属性
  • 配置文件的格式有多种,propertiesxmlyaml、以及yml
  • 配置文件的位置有多种,classpath:/classpath:/config/file:./file:./config/file:./config/*/、以及指定的位置spring.config.location
  • 最后将contributor对象集合中的配置属性再应用到运行环境中。

点此进入上一集:配置扩展属性(上)




纸上得来终觉浅,绝知此事要躬行。

————————我是万万岁,我们下期再见————————

相关文章
|
2月前
|
Java Spring
Spring Boot配置的优先级?
在Spring Boot项目中,配置可通过配置文件和外部配置实现。支持的配置文件包括application.properties、application.yml和application.yaml,优先级依次降低。外部配置常用方式有Java系统属性(如-Dserver.port=9001)和命令行参数(如--server.port=10010),其中命令行参数优先级高于系统属性。整体优先级顺序为:命令行参数 &gt; Java系统属性 &gt; application.properties &gt; application.yml &gt; application.yaml。
559 0
|
9天前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
158 4
|
16天前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
探索Spring Boot的@Conditional注解的上下文配置
|
1月前
|
安全 算法 Java
在Spring Boot中应用Jasypt以加密配置信息。
通过以上步骤,可以在Spring Boot应用中有效地利用Jasypt对配置信息进行加密,这样即使配置文件被泄露,其中的敏感信息也不会直接暴露给攻击者。这是一种在不牺牲操作复杂度的情况下提升应用安全性的简便方法。
677 10
|
6月前
|
缓存 Java API
微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的配置
本文介绍了在Spring Boot中配置Swagger2的方法。通过创建一个配置类,添加`@Configuration`和`@EnableSwagger2`注解,使用Docket对象定义API文档的详细信息,包括标题、描述、版本和包路径等。配置完成后,访问`localhost:8080/swagger-ui.html`即可查看接口文档。文中还提示了可能因浏览器缓存导致的问题及解决方法。
710 0
微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的配置
|
6月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
917 0
|
2月前
|
人工智能 安全 Java
Spring Boot yml 配置敏感信息加密
本文介绍了如何在 Spring Boot 项目中使用 Jasypt 实现配置文件加密,包含添加依赖、配置密钥、生成加密值、在配置中使用加密值及验证步骤,并提供了注意事项,确保敏感信息的安全管理。
774 1
|
3月前
|
Java API 数据库
JPA简介:Spring Boot环境下的实践指南
上述内容仅是JPA在Spring Boot环境下使用的冰山一角,实际的实践中你会发现更深更广的应用。总而言之,只要掌握了JPA的规则,你就可以借助Spring Boot无比丰富的功能,娴熟地驾驶这台高性能的跑车,在属于你的程序世界里驰骋。
155 15
|
6月前
|
Java 数据库连接 数据库
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——MyBatis 介绍和配置
本文介绍了Spring Boot集成MyBatis的方法,重点讲解基于注解的方式。首先简述MyBatis作为持久层框架的特点,接着说明集成时的依赖导入,包括`mybatis-spring-boot-starter`和MySQL连接器。随后详细展示了`properties.yml`配置文件的内容,涵盖数据库连接、驼峰命名规范及Mapper文件路径等关键设置,帮助开发者快速上手Spring Boot与MyBatis的整合开发。
854 0
|
6月前
|
缓存 Java 应用服务中间件
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——依赖导入和Thymeleaf相关配置
在Spring Boot中使用Thymeleaf模板,需引入依赖`spring-boot-starter-thymeleaf`,并在HTML页面标签中声明`xmlns:th=&quot;http://www.thymeleaf.org&quot;`。此外,Thymeleaf默认开启页面缓存,开发时建议关闭缓存以实时查看更新效果,配置方式为`spring.thymeleaf.cache: false`。这可避免因缓存导致页面未及时刷新的问题。
254 0