前言

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

正文

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

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

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

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

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

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

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

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

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

知道了日志打印来自 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_PROPERTYCONFIG_LOCATION_PROPERTY等。从类的注释可以看出,Spring Boot 程序启动所加载的 application.propertiesapplication.yml 默认从四个路径下加载,我们最常用的就是最后一种,它也可以告诉我们还可以把配置文件放在哪,如何自定义加载配置文件的路径。

  • file:./config/:
  • file:./
  • classpath:config/
  • classpath:

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

这里的 LoaderConfigFileApplicationListener类内部私有类,用于协调属性源和配置 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 下,所以只要留意当 locationclasspath:/ 的程序执行情况即可。

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

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

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

!](http://ww3.sinaimg.cn/large/006tNc79ly1g5x80ivld4j31rk0qg130.jpg)

并且 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工具调试功能,在错综复杂的代码中能更准确地定位目标。

推荐阅读

源码解读 Spring Boot Profiles的更多相关文章

  1. 从源码看Spring Boot 2.0.1

    Spring Boot 命名配置很少,却可以做到和其他配置复杂的框架相同的功能工作,从源码来看是怎么做到的. 我这里使用的Spring Boot版本是 2.0.1.RELEASE Spring Boo ...

  2. 头秃了,二十三张图带你从源码了解Spring Boot 的启动流程~

    持续原创输出,点击上方蓝字关注我 目录 前言 源码版本 从哪入手? 源码如何切分? 如何创建SpringApplication? 设置应用类型 设置初始化器(Initializer) 设置监听器(Li ...

  3. Spring:源码解读Spring IOC原理

    Spring IOC设计原理解析:本文乃学习整理参考而来 一. 什么是Ioc/DI? 二. Spring IOC体系结构 (1) BeanFactory (2) BeanDefinition 三. I ...

  4. Spring源码解读Spring IOC原理

    一.什么是Ioc/DI? IoC 容器:最主要是完成了完成对象的创建和依赖的管理注入等等. 先从我们自己设计这样一个视角来考虑: 所谓控制反转,就是把原先我们代码里面需要实现的对象创建.依赖的代码,反 ...

  5. 从源码解读Spring如何解决bean循环依赖

    1 什么是bean的循环依赖 循环依赖的原文是circular reference,指多个对象相互引用,形成一个闭环. 以两个对象的循环依赖为例: Spring中的循环依赖有 3 种情况: 构造器(c ...

  6. 从源码看Spring Security之采坑笔记(Spring Boot篇)

    一:唠嗑 鼓捣了两天的Spring Security,踩了不少坑.如果你在学Spring Security,恰好又是使用的Spring Boot,那么给我点个赞吧!这篇博客将会让你了解Spring S ...

  7. Spring源码-循环依赖源码解读

    Spring源码-循环依赖源码解读 笔者最近无论是看书还是从网上找资料,都没发现对Spring源码是怎么解决循环依赖这一问题的详解,大家都是解释了Spring解决循环依赖的想法(有的解释也不准确,在& ...

  8. spring IOC DI AOP MVC 事务, mybatis 源码解读

    demo https://gitee.com/easybao/aop.git spring DI运行时序 AbstractApplicationContext类的 refresh()方法 1: pre ...

  9. Spring事务源码解读

    一.Spring事务使用 1.通过maven方式引入jar包 <dependency> <groupId>com.alibaba</groupId> <art ...

随机推荐

  1. 页面的隐藏或显示:hidden与visibilityState

    我们在很多地方都需要判断用户是不是在当前页面,如果离开了当前页面我们需要捕捉到并进行一些操作. 例如:当视频处于播放状态时,我们需要判断用户是不是在当前页面以继续播放,如果离开了我们需要暂停播放. 有 ...

  2. java操作mongo

    语法正确时,字段不匹配时,Mongo并不会抛出异常,这在语句调试时需多加注意. mongo自身的时间存储格式与java中的并不是完全匹配,Mongo采用UTC格式,而java中一般为GMT格式,有个时 ...

  3. 原创:Python编写通讯录,支持模糊查询,利用数据库存储

    1.要求 数据库存储通讯录,要求按姓名/电话号码查询,查询条件只有一个输入入口,自动识别输入的是姓名还是号码,允许模糊查询. 2.实现功能 可通过输入指令进行操作. (1)首先输入“add”,可以对通 ...

  4. 基于百度EasyDL定制化图像识别平台的海洋鱼类识别方法

    [目的]鱼类识别对渔业资源的开发利用有着重要的意义.针对海底环境恶劣.拍摄环境亮度低.场景模糊的实际情况导致海底观测视频品质差,视频中的鱼类识别难的问题以及现有鱼类识别方法存在的鱼类标注数据集过少导致 ...

  5. MediatR-进程内的消息通信框架

    MediatR是一款进程内的消息订阅.发布框架,提供了Send方法用于发布到单个处理程序.Publish方法发布到多个处理程序,使用起来非常方便.目前支持 .NET Framework4.5..NET ...

  6. EF简介及CRUD简单DEMO

    一.实体框架(Entity FrameWork)简介 • 简称EF • 与Asp.Net MVC关系与ADO.NET关系 • ADO.NET Entity FrameWork是微软以ADO.NET为基 ...

  7. 十三、asp.net中Repeater控件用法笔记

    大家可能都对datagrid比较熟悉,但是如果在数据量大的时候,我们就得考虑使用 repeater作为我们的数据绑定控件了.Repeater控件与DataGrid (以及DataList)控件的主要区 ...

  8. Visual Studio 调试(系列文章)

    调试是软件开发过程中非常重要的一个部分,它具挑战性,但是也有一定的方法和技巧. Visual Studio 调试程序有助于你观察程序的运行时行为并发现问题. 该调试器可用于所有 Visual Stud ...

  9. Java EE.Servlet.会话管理

    一次会话是从客户打开浏览器开始到关闭浏览器结束.记录会话信息的技术称为会话跟踪.常见的会话跟踪技术有Cookie.URL重写和隐藏表单域. 1.Cookie Cookie是一小块可以嵌入到HTTP请求 ...

  10. JavaScript ES6 Promiss对象

    说明 Node.js中,以异步(Async)回调著称,使用了异步,提高了程序的执行效率,但是,代码可读性较差的. 假如有几个异步操作,后一个操作需要前一个操作的执行完毕之后返回的数据才能执行下去,如果 ...