前言

上文《一文掌握 Spring Boot Profiles》 是对 Spring Boot Profiles 的介绍和使用,因此本文将从源码角度探究 Spring Boot Profiles,让我们看下 Spring Boot 底层是如何应用 Profiles 进行环境配置的隔离与生效的。

正文

首先,我们先来看下一个简单的 Spring Boot 示例程序,

源码解读 Spring Boot Profiles_Spring Boot

在主程序方法中,打印容器中获取到 User 对象,它只有一个 name 属性。

源码解读 Spring Boot Profiles_Spring Boot_02

这里 name 属性引用了外部配置 user.username 的值,它是从配置文件中读取,这里我定义两个配置文件设置该属性,application.properties 和 application-prod.properties

源码解读 Spring Boot Profiles_Spring Boot_03

有了配置文件之后,启动 SimapleSpringApplication 程序,我们首先可以看到日志输入:User Bean: User(name=one),由此可以看出程序读取了 application.properties 的 user.username配置。现在我们在 application.properties 中加入一行:

源码解读 Spring Boot Profiles_Spring Boot_04

再次重启启动程序,可以看到控制台如下日志:

源码解读 Spring Boot Profiles_Spring Boot_05

此时 User 对象的name属性变成了 application-prod.properties 中定义的值,并且日志提示 The following profiles are active: prod 表明了名称为 prod 的Profile 在程序中激活。接下来我们就从这个日志入手,探究下这一切是如何发生的。

首先,根据 IDE 的全局查找功能,直接搜索 The following profiles are active: 这些词出现的位置,进行定位,可以找到这个日志出现于 SpringApplication#logStartupProfileInfo 方法之中。

源码解读 Spring Boot Profiles_Spring Boot_06源码解读 Spring Boot Profiles_Spring Boot_07

从日志方法可以看出打印的 activeProfiles 来自上下文关联的 environment 对象,再进一步查看 logStartupProfileInfo 的调用位置,可以在 SpringApplication#prepareContext 方法之中找到,这个方法从命名上就可以看出是主要负责 Spring Boot 运行前容器上下文的预备工作,

源码解读 Spring Boot Profiles_Spring Boot_08

我们重新运行程序,通过断点方式拦截 SpringApplication#prepareContext 方法的指向, 获取 environment对象真实的类型为 StandardEnvironment,是 Environment 接口非Web环境的标准实现,存储着一些应用配置和 Profiles 信息,如果在Web环境下,context 关联的就是 StandardServletEnvironment 类型的对象。

源码解读 Spring Boot Profiles_Spring Boot_09

知道了日志打印来自 StandardEnvironment 对象的 activeProfiles 属性之后,就需要来看它是在什么时间被赋值的了。继续从调用链的上一级查找,就到了 SpringApplication#run(java.lang.String...),这也是整个程序启动的主要方法。

源码解读 Spring Boot Profiles_Spring Boot_10

从图中可以看出第一次获取到的 environment 对象来自 SpringApplication#prepareEnvironment内部生成, prepareEnvironment 方法内部首先通过 getOrCreateEnvironment 获取一个基础的 ConfigurableEnvironment 实例,然后对该实例对象初始化配置返回。

源码解读 Spring Boot Profiles_Spring Boot_11

正在创建 environment 对象来自 SpringApplication#getOrCreateEnvironment,看它的实现就可以验证我们之前提到 environment 对象类型为 StandardEnvironment。

源码解读 Spring Boot Profiles_Spring Boot_12

了解完 environment 的创建,接下来就关注 environment 的初始化了,这里我们需要关注 listeners.environmentPrepared(environment) 这行代码,这里的 listeners 为 SpringApplicationRunListeners 实例,是监听器 SpringApplicationRunListener 的集合对象, SpringApplicationRunListener#environmentPrepared 方法中就是对每个 SpringApplicationRunListener 对象遍历指向类似的 environmentPrepared 方法,当前集合中只有一个 EventPublishingRunListener 实例,查看其 environmentPrepared 方法就可以看到它主要就是用于发布包含 environment 实例的 ApplicationEnvironmentPreparedEvent 事件,让其他所有监听该事件的监听器进行 environment 实例的配置。

源码解读 Spring Boot Profiles_Spring Boot_13

事件对象 ApplicationEnvironmentPreparedEvent 还有一个 getEnvironment 方法获取所传递的 environment实例,我们可以通过看这个方法被使用的地方,获取有哪些类在配置 environment 对象。

源码解读 Spring Boot Profiles_Spring Boot_14

经过多次的查看,从上图可以定位到 ConfigFileApplicationListener 类内的方法对 environment 对象进行扩展,从命名可以看出这个监听器跟配置文件相关,比如它的一些常量属性:CONFIG_NAME_PROPERTYCONFIG_LOCATION_PROPERTY等。从类的注释可以看出,Spring Boot 程序启动所加载的 application.properties 或 application.yml 默认从四个路径下加载,我们最常用的就是最后一种,它也可以告诉我们还可以把配置文件放在哪,如何自定义加载配置文件的路径。

  • file:./config/:

  • file:./

  • classpath:config/

  • classpath:

源码解读 Spring Boot Profiles_Spring Boot_15

将程序断点设置于 ConfigFileApplicationListener#onApplicationEvent 方法之内,重新运行程序就看到程序此时运行到了 ConfigFileApplicationListener 类之中,内部经过多个方法调用从onApplicationEvent 来到了 addPropertySources 方法,这个方法就是配置文件的属性源加载到 environment 环境去的。

源码解读 Spring Boot Profiles_Spring Boot_16

这里的 Loader 是 ConfigFileApplicationListener类内部私有类,用于协调属性源和配置 Profiles,我们再进一步跟踪到它的 load 方法。

源码解读 Spring Boot Profiles_Spring Boot_17

我们主要看这个方法中的是三个方法:

  • Loader#initializeProfiles

  • Loader#addProfileToEnvironment

  • Loader#load(Profile, DocumentFilterFactory, DocumentConsumer)

第一个方法 initializeProfiles 初始化 Profiles,给 profiles 属性添加两个元素,null 和 默认的Profile。

第二个方法 addProfileToEnvironment 就是将 Profile 添加到 environment 对象的 activeProfiles 里,也就是最开始日志打印的 activeProfiles

第三个方法就是加载配置文件的数据源和 Profies 相关的属性。

进入 load 方法,这个方法内部通过不同配置路径去尝试执行另一个 load 方法加载配置文件,这里 name 就是配所要搜索的配置文件名称,默认为 application

源码解读 Spring Boot Profiles_Spring Boot_18

由于我们的配置文件在 ClassPath 下,所以只要留意当 location 为 classpath:/ 的程序执行情况即可。

源码解读 Spring Boot Profiles_Spring Boot_19

由于SpringBoot 配置文件支持xmlproperties, yml 格式,就需要不同 PropertySourceLoader 支持其文件内容的加载:PropertiesPropertySourceLoader 支持 xmlproperties 文件,YamlPropertySourceLoader 支持 yml 文件,加载以 .yml 或 .yaml 后缀的文件,Loader#loadForFileExtension 方法就完成了对这些配置文件的加载。

我们示例程序只有 properties 文件,所以只需要关注当 loader 为 PropertiesPropertySourceLoader时的 Loader#loadForFileExtension 方法的执行情况。

源码解读 Spring Boot Profiles_Spring Boot_20

loadForFileExtension 内部调用另外一个加载配置文件的 load 方法,当读取到ClassPath下的application.properties 时,会执行到 Loader#loadDocuments 方法,这个方法就是把配置文件作为文档进行加载,所有键值对配置都会存在 PropertySource 之中,存储到 Document 对象中。

源码解读 Spring Boot Profiles_Spring Boot_21

并且 documents 对象经过 Loader#asDocuments 方法关联上 spring.profiles.active 属性,profiles 属性添加一个定义为 prod 的 Profile,为后面的 Environment 对象添加 Profile 做准备,到这里默认的配置文件 application.properties 加载完毕了,方法又回到了 Loader#load() 上。

源码解读 Spring Boot Profiles_Spring Boot_22

有了新添加的 Profile,继续进入循环,就会通过 Loader#addProfileToEnvironment 方法,为 environment 对象保存激活的 Profile,并且按照之前的逻辑,读取名为 application-prod.properties 的配置文件,命名方式可以从之前的 Loader#loadForFileExtension 的第462行就可以看出:

源码解读 Spring Boot Profiles_Spring Boot_23

在 Loader#load() 方法读取了所有配置文件后,执行 Loader#addLoadedPropertySources,将对应属性源 PropertySource 存储到 environment 对象中,并且 application-prod.properties 顺序先于默认配置文件,就是为了后面程序应用相同名称配置的时候,优先采用元素位置在前的配置。

源码解读 Spring Boot Profiles_Spring Boot_24

至此,所有配置文件上的数据加载完存储到了与当前上下文关联的 environment 对象中,将 prod 作为 Active Profile 激活特定环境配置的工作就完成了。

小结

虽然只是探究 Spring Boot 程序如何加载和应用 Profile,但通过这次源码分析,我们可以发现 SpringBoot 虽简单易用,但是内部实现逻辑设计是比较复杂的,无论是资源的加载,数据的解析都有专门的组件类去处理,大量使用事件通知和设计模式,在分析源码时少不了一次又一次的运行断点,不过这需要我们充分利用DE工具调试功能,在错综复杂的代码中能更准确地定位目标。

>>阿里云8月最新优惠,点击查看<<

留言交流不过瘾?添加微信:zyc_enjoy

根据指引加入各种主题讨论群

 

每日一问

今日问题赌局现在到了最后决出胜负的关键时刻。

蒋老大非常幸运地赢了700个金条,现居第名。第二名的贾老大稍微落后.赢了500个金条。其余的人都已经输光了。

蒋老大犹豫着,要将手上的筹码押部分在“偶数”或“奇数”上,赢的话赌金就可以变成两倍。另边,贾老大已经把所有筹码部押在“三的倍数”上,赢的话赌金可以变成三倍,运气好的话他就可以反败为胜。

请问:蒋老大应该怎么下注才能稳操胜券呢?

(留言说说你的答案和解析吧,关注公众号,发送口令:Q20190819,核对正确答案)

 

昨日问答:点击>>查看<<

 

推荐阅读

 

签到计划

活动介绍自律到极致-人生才精致:第12期

活动奖励:《Java微服务测试》* 10

 

扫描下方二维码,参与签到

源码解读 Spring Boot Profiles_Spring Boot_25

 

源码解读 Spring Boot Profiles_Spring Boot_26