Spring Boot-13-Spring Profile解析

在日常开发中,有这样一种情况,就是开发环境,测试环境,生产环境,所需要的配置不同,我们通常会使用不同的配置文件来解决这个问题,比如开发环境我们有一个 application-dev.yml 的配置,然后生产环境有一个 application-prod.yml 的配置, 然后再不同的环境中,我们可以通过配置 spring.profiles.active 这个属性,来指定哪个配置文件生效

相关配置

这里先介绍几个常用的 profile 相关的配置

1
2
# 指定生效的配置 
spring.profiles.active=xx
1
2
# 默认生效的配置(这里要注意,如果配置了 spring.profiles.active 则 spring.profiles.default 不生效,配置在application配置文件中也不生效)
spring.profiles.default=xx
1
2
# 配置多个配置文件同时生效
spring.profiles.include=xx,xx
1
2
# 指定profile前缀(默认就是application,这个配置要写在命令行中才会生效)  
spring.config.name=xx

源码解析

在上一篇文章中我们了解了属性加载的整体流程, 其中 ConfigFileApplicationListener 这个类实现了 EnvironmentPostProcessor ,然后他的 addPropertySources() 方法中,首先加了一个随机数的属性集,然后就是调用了 Loader.load() 这个方法,具体如图:

image

这里我们重点关注两个方法:

  • initializeProfiles()
  • load()

一个一个看,先进入 initializeProfiles() 这个方法中

initializeProfiles()

image

getDefaultProfiles()

这里进入 this.environment.getDefaultProfiles() 这个方法,看一下他是怎么获取默认的 profile 的

image

这里先是用 this.defaultProfilesgetReservedDefaultProfiles() 这方法的返回值做了一个对比, 我们看一下这两个东西的值到底是什么

image

可以看到,这里的 defaultProfiles 其实也是调用的 getReservedDefaultProfiles() 这个方法,如下:

image

这里就是一个单元素集合,值是 default ,这里判断对比一致之后,找我们配置中有没有 spring.profiles.default ,有的话进入 setDefaultProfiles() 这个方法,进去看一下

image

所以 getDefaultProfiles() 这个方法做的事情就是,从配置中找我们有没有配置 spring.profiles.default ,有的话用我们自定义配置的,没有的话,返回默认的 default

回到上面的 initializeProfiles() 看一下,只有在 profiles 集合size是1的情况下,才会去找默认的profiles, 也就解释了配置了 spring.profiles.active 之后, spring.profiles.default 不会生效的原因

综上所述,如果我们项目中,什么也没有配置的话, initializeProfiles() 这个方法执行完毕后, profiles 这个集合中应该会有两个属性, 一个最开始添加的 null ,一个是默认的 defaultProfile 对象

load()

回到 Loader.load 方法中

image

initializeProfiles() 走完之后, profiles 集合不为空,然后进入循环, poll() 方法弹出集合中的第一个元素,第一次循环应该是最先添加的 null , 然后做一个判断 isDefaultProfile() 是否是默认的 profile, null 显然不是,就会进入下面的 load() 方法,我们看一下这个方法的源码

image

getSearchLocations()

这里首先调用的是 getSearchLocations() 获取一个路径的集合,点进去看一下

image

这里的这个默认路径 DEFAULT_SEARCH_LOCATIONS 如下

image

一共 4 个路径, classpath 的根目录和 classpath 下的 /config 目录,还有文件的根目录和文件目录下的 /config 目录

getSearchNames()

再来看一下 getSearchNames() 的源码

image

获取到名称之后,再次循环,然后调用同名 load 方法,点进去

image

这里打了一个断点, 这里有两个 propertySourceLoaders ,然后第二层循环中,会调用他们的 getFileExtensions() 方法,我们分别看一下这两个方法的返回

首先是 PropertiesPropertySourceLoader

image

然后是 YamlPropertySourceLoader

image

所以这里 PropertiesPropertySourceLoader 是处理 propertiesxml 两种格式的文件, YamlPropertySourceLoader 是处理 ymlyaml 两种格式的文件

然后最终会调用 loadForFileExtension() 这个方法,点进去看一下

image

这里就是拼接一下配置文件名,然后最终又是调用了一个 load() 方法, 进去看一下

image

这里的重点是断点处的这个 loadDocument() 方法,这方法会把我们的配置文件解析出来, 然后转成 Document 对象, 点进去看一下

image

这里在这个 loader.load 方法打个断点运行看一下

image

这里就是我们项目中的 application.yml 解析出来的一个 key-value 集合,然后下面调用的了 asDocuments() 方法,点进去

image

这里做了两件事, 首先是通过 Binder 对象,将配置绑定到对应的类中, 然后创建了 Document 对象, 我们看一下他调用的这个构造是赋了哪些值

image

这里的 activeProfilesincludeProfiles 就是上面在配置文件中解析出来的 spring.profiles.activespring.profiles.include 的值, 最终这个 Document 集合返回之后,会循环然后把他们的 activeProfilesincludeProfiles 都添加到 this.profiles 集合中去, 这里就解释了为什么我们在配置文件中设置的 spring.profiles.activespring.profiles.include 也会生效

然后回到最开始的load方法中

image

这个循环会处理完所有的profile,包括在配置文件中读取出来的 profile ,然后最后又去处理了一次profile是null的情况,主要是防止上面的循环没进, 读取一次 application.yml 或者 application.properties

之后会调用一个 addLoadedPropertySources() 方法, 进去看一下

image

这里就把所有读取到的属性都加到属性集中去

到这里,配置文件的解析就完成了

总结

回头来画画图总结一下配置文件加载的流程

加载入口

image

initializeProfiles() 方法流程图

image

profiles集合处理流程

image

load() 方法流程图

image

配置文件读取流程图

image

addLoadedPropertySources() 方法流程图

image