上文《一文掌握 Spring Boot Profiles》 是对 Spring Boot Profiles 的介绍和使用,所以本文将从源码角度探究 Spring Boot Profiles,让咱们看下 Spring Boot 底层是如何应用 Profiles 进行环境配置的隔离与生效的。java
首先,咱们先来看下一个简单的 Spring Boot 示例程序,spring
在主程序方法中,打印容器中获取到 User 对象,它只有一个 name
属性。设计模式
这里 name
属性引用了外部配置 user.username
的值,它是从配置文件中读取,这里我定义两个配置文件设置该属性,application.properties
和 application-prod.properties
。app
有了配置文件以后,启动 SimapleSpringApplication
程序,咱们首先能够看到日志输入:User Bean: User(name=one)
,由此能够看出程序读取了 application.properties
的 user.username
配置。如今咱们在 application.properties
中加入一行:微服务
再次重启启动程序,能够看到控制台以下日志:工具
此时 User 对象的name属性变成了 application-prod.properties
中定义的值,而且日志提示 The following profiles are active: prod
代表了名称为 prod
的Profile 在程序中激活。接下来咱们就从这个日志入手,探究下这一切是如何发生的。源码分析
首先,根据 IDE 的全局查找功能,直接搜索 The following profiles are active:
这些词出现的位置,进行定位,能够找到这个日志出现于 SpringApplication#logStartupProfileInfo
方法之中。spa
从日志方法能够看出打印的 activeProfiles
来自上下文关联的 environment
对象,再进一步查看 logStartupProfileInfo
的调用位置,能够在 SpringApplication#prepareContext
方法之中找到,这个方法从命名上就能够看出是主要负责 Spring Boot 运行前容器上下文的预备工做,设计
咱们从新运行程序,经过断点方式拦截 SpringApplication#prepareContext
方法的指向, 获取 environment
对象真实的类型为 StandardEnvironment,是 Environment 接口非Web环境的标准实现,存储着一些应用配置和 Profiles 信息,若是在Web环境下,context 关联的就是 StandardServletEnvironment 类型的对象。3d
知道了日志打印来自 StandardEnvironment 对象的 activeProfiles
属性以后,就须要来看它是在什么时间被赋值的了。继续从调用链的上一级查找,就到了 SpringApplication#run(java.lang.String...)
,这也是整个程序启动的主要方法。
从图中能够看出第一次获取到的 environment
对象来自 SpringApplication#prepareEnvironment
内部生成, prepareEnvironment
方法内部首先经过 getOrCreateEnvironment
获取一个基础的 ConfigurableEnvironment
实例,而后对该实例对象初始化配置返回。
正在建立 environment
对象来自 SpringApplication#getOrCreateEnvironment
,看它的实现就能够验证咱们以前提到 environment
对象类型为 StandardEnvironment。
了解完 environment
的建立,接下来就关注 environment
的初始化了,这里咱们须要关注 listeners.environmentPrepared(environment)
这行代码,这里的 listeners
为 SpringApplicationRunListeners 实例,是监听器 SpringApplicationRunListener 的集合对象, SpringApplicationRunListener#environmentPrepared
方法中就是对每一个 SpringApplicationRunListener 对象遍历指向相似的 environmentPrepared
方法,当前集合中只有一个 EventPublishingRunListener 实例,查看其 environmentPrepared
方法就能够看到它主要就是用于发布包含 environment
实例的 ApplicationEnvironmentPreparedEvent
事件,让其余全部监听该事件的监听器进行 environment
实例的配置。
事件对象 ApplicationEnvironmentPreparedEvent 还有一个 getEnvironment
方法获取所传递的 environment
实例,咱们能够经过看这个方法被使用的地方,获取有哪些类在配置 environment
对象。
通过屡次的查看,从上图能够定位到 ConfigFileApplicationListener 类内的方法对 environment
对象进行扩展,从命名能够看出这个监听器跟配置文件相关,好比它的一些常量属性:CONFIG_NAME_PROPERTY
,CONFIG_LOCATION_PROPERTY
等。从类的注释能够看出,Spring Boot 程序启动所加载的 application.properties
或 application.yml
默认从四个路径下加载,咱们最经常使用的就是最后一种,它也能够告诉咱们还能够把配置文件放在哪,如何自定义加载配置文件的路径。
file:./config/:
file:./
classpath:config/
classpath:
将程序断点设置于 ConfigFileApplicationListener#onApplicationEvent
方法以内,从新运行程序就看到程序此时运行到了 ConfigFileApplicationListener
类之中,内部通过多个方法调用从 onApplicationEvent
来到了 addPropertySources
方法,这个方法就是配置文件的属性源加载到 environment
环境去的。
这里的 Loader
是 ConfigFileApplicationListener
类内部私有类,用于协调属性源和配置 Profiles,咱们再进一步跟踪到它的 load 方法。
咱们主要看这个方法中的是三个方法:
Loader#initializeProfiles
Loader#addProfileToEnvironment
Loader#load(Profile, DocumentFilterFactory, DocumentConsumer)
第一个方法 initializeProfiles
初始化 Profiles,给 profiles
属性添加两个元素,null
和 默认的Profile。
第二个方法 addProfileToEnvironment
就是将 Profile 添加到 environment
对象的 activeProfiles
里,也就是最开始日志打印的 activeProfiles
。
第三个方法就是加载配置文件的数据源和 Profies 相关的属性。
进入 load
方法,这个方法内部经过不一样配置路径去尝试执行另外一个 load
方法加载配置文件,这里 name
就是配所要搜索的配置文件名称,默认为 application
。
因为咱们的配置文件在 ClassPath 下,因此只要留意当 location
为 classpath:/
的程序执行状况便可。
因为SpringBoot 配置文件支持xml
,properties
, yml
格式,就须要不一样 PropertySourceLoader 支持其文件内容的加载:PropertiesPropertySourceLoader 支持 xml
,properties
文件,YamlPropertySourceLoader 支持 yml 文件,加载以 .yml
或 .yaml
后缀的文件,Loader#loadForFileExtension
方法就完成了对这些配置文件的加载。
咱们示例程序只有 properties
文件,因此只须要关注当 loader
为 PropertiesPropertySourceLoader时的 Loader#loadForFileExtension
方法的执行状况。
loadForFileExtension
内部调用另一个加载配置文件的 load
方法,当读取到ClassPath下的application.properties
时,会执行到 Loader#loadDocuments
方法,这个方法就是把配置文件做为文档进行加载,全部键值对配置都会以存在 PropertySource 之中,存储到 Document 对象中。
!](ww3.sinaimg.cn/large/006tN…)
而且 documents
对象通过 Loader#asDocuments
方法关联上 spring.profiles.active
属性,profiles
属性添加一个定义为 prod
的 Profile,为后面的 Environment 对象添加 Profile 作准备,到这里默认的配置文件 application.properties
加载完毕了,方法又回到了 Loader#load()
上。
有了新添加的 Profile,继续进入循环,就会经过 Loader#addProfileToEnvironment
方法,为 environment
对象保存激活的 Profile,而且按照以前的逻辑,读取名为 application-prod.properties
的配置文件,命名方式能够从以前的 Loader#loadForFileExtension
的第462行就能够看出:
在 Loader#load()
方法读取了全部配置文件后,执行 Loader#addLoadedPropertySources
,将对应属性源 PropertySource 存储到 environment
对象中,而且 application-prod.properties
顺序先于默认配置文件,就是为了后面程序应用相同名称配置的时候,优先采用元素位置在前的配置。
至此,全部配置文件上的数据加载完存储到了与当前上下文关联的 environment
对象中,将 prod
做为 Active Profile 激活特定环境配置的工做就完成了。
虽然只是探究 Spring Boot 程序如何加载和应用 Profile,但经过此次源码分析,咱们能够发现 SpringBoot 虽简单易用,可是内部实现逻辑设计是比较复杂的,不管是资源的加载,数据的解析都有专门的组件类去处理,大量使用事件通知和设计模式,在分析源码时少不了一次又一次的运行断点,不过这须要咱们充分利用DE工具调试功能,在错综复杂的代码中能更准确地定位目标。