浅谈如何将Spring Boot的启动配置放在配置管理系统中
楔子
俗话说得好,没有需求就没有变通。之前所在项目的技术栈中并没有用到spring boot,后来一次的产品重构中在某个服务中便添加进去了。在业务开发中spring boot用着确实挺爽的,但是由于这和之前的产品风格不一致,导致在运维部署的时候带来了一些额外的负担 – spring boot的应用配置信息需要在本地配置文件文件中写死。这样就得在不同的环境中进行修改。这时候有人肯定会想可以使用spring boot提供的多环境配置,这个方式确实在3-5个环境中还可以这样玩,一旦环境数量达到几十个的时候,这样配置会是一种灾难。
为了解决这种多环境下的配置信息管理问题,我们决定将spring boot的启动配置信息转移到统一配置管理系统中(这里采用的是zookeeper,其他的方式也类似)。这也是因为其他产品的配置信息也都是在zookeeper中进行管理的。
正文
其实实现的思路很简单,就是在启动配置文件application.properties被加载以前,根据从zookeeper中获取到的信息组装成一份完整的application.properties文件。
一、错误的方案
一开始我们在没有根据的情况的下凭空臆断 – application.properties配置文件是在spring容器被加载完毕后读取的。此时的代码被写成了以下形式。
这里创建了一个用来监听Spring容器是否被加载的监听器,然后在加载成功以后执行从zookeeper获取配置信息,然后再生成application.properties的逻辑。此时的运行结果就是系统找不到application.properties,无法正确启动。此时我们意识到application.properties文件的加载时间并不是所想象的那样。
二、application.properties的加载逻辑
为了让spring boot可以顺利的解析到我们动态生成的application.properties,我们需要打开spring boot的源码来仔细研究一下其加载的正确时间。
这里有人会说,难道不能在spring boot启动之前就获取配置信息然后生成application.properties文件吗?回答是肯定可以的(例如以下这种写法),但是如果真的这么做了,这篇文章不就没有那么多干货了。
打开源码第一层,可以看到如下代码
- 这里创建了一个SpringApplication的对象,初始化了一系列的成员变量
- 调用了该对象的一个核心方法run
这里SpringApplication的初始化不是本文的重点,这里就略过,我们现在来看看run方法里面都做了些什么事情
里面的内容比较多,这里只挑一些比较关键的且与配置文件加载相关的部分来说明
- 这里创建了一个SpringApplicationRunListeners它是一个SpringApplicationRunListener的集合,用来在spring boot的不同生命周期,广播相应的事件
- 在run一开始的时间广播
- 在context刷新完成后,run结束前广播
- 实例化一个全局的系统参数对象
- 创建spring boot的配置环境,也就是在这里会加载application.properties
这会让我们来看一下(5)究竟一些什么事情
- 获取一个环境配置信息
- 装载配置信息
- SpringApplicationRunListeners再次在环境建立好的时候广播消息,此时所有监听ApplicationEnvironmentPreparedEvent事件的监听器都会被触发。
这时我们追踪到了和application.properties相关的监听器为ConfigFileApplicationListener
关于onApplicationEnvironmentPreparedEvent方法里面做的事情这里简要说明一下。EnvironmentPostProcessor是spring boot提供动态管理配置文件的扩展接口,支持自己去扩展。这里ConfigFileApplicationListener也被当做是扩展的一种(实现了EnvironmentPostProcessor接口),所以this也被添加到扩展集合中。后面便是将这些扩展进行排序,依次调用他们所定义的postProcessEnvironment方法。
这里我们尤为需要关心的是this所提供的postProcessEnvironment方法
调用load前的部分可以理解为将配置信息添加到环境配置中,这里不做详细研究,重点我们来看一下load里面的逻辑
- 创建一个配置加载器
- 根据规则加载配置信息
- 默认遍历classpath:/,classpath:/config/,file:./,file:./config/路径下的所有文件
- 在(3)中指定的路径下面扫描指定文件名称的配置文件,这里默认是application
- 这里是加载配置文件的核心逻辑,下面再进行讲解
- 最后将加载到的配置文件信息全部添加至环境配置中
下面让我们快速看一下(5)所描述的核心调用链
虽然ConfigFileApplicationListener监听器执行的逻辑有些绕,但是概括起来就是在指定的路径下扫描指定名称的配置文件,然后根据文件类型调用不同的配置文件加载器,最后将解析出来的内容放置在环境配置中,以供spring boot后续使用。
三、结论
现在我们可以很明确知道application.properties配置的正确加载时间,就是spring boot在触发ApplicationEnvironmentPreparedEvent事件之后。因此我们可以自己实现一个监听器用来监听ApplicationEnvironmentPreparedEvent事件的发生,来执行我们生成application.properties配置文件的逻辑。
实现逻辑可以参照如下所示
至此整个分析过程已经全部结束。现在做一下下期预告,我们将从一次差点被玩坏的Zookeeper来讲讲关于它的权限控制
欢迎关注微信公众号,在这里可以提前看到下一期文章哦~