喜欢写代码, 讨厌配环境
我相信这十个字的小标题代表了大多数码农的心声.
十年前读大学时, 学校开设了 C 语言还有 C++. 但是学习这两种语言, 对于新手来说非常没有成就感.
于是我就在校门口买个光盘, 装个 VS(宇宙第一 IDE), 还有离线中文版 MSDN(最牛的帮助文档), 万事已俱备.
学习 C# 语法, 看类的 API, 然后从 WinForm(窗口)开始, 用鼠标拖拽控件, 设置控件属性, 观察自动生成的代码, 开启人生的编程之路.
大四的时候接触到 Java, 首先就是配置环境变量, 那时觉得是一个巨复杂的东西, 每次都要网上搜好一会儿才能配好.
我学习微软的东西从来就不要配什么环境变量, 心里很讨厌这个 Java 的环境变量, 这就导致十年后的今天, 我依然要去网上搜如何配置, 惭愧惭愧, 哈哈.
后来发现, 基本上软件都要配置一些环境变量, 只不过有的是在安装时已经自动配好了而已, 但是对于免安装版 (直接解压) 的则需要自己配.
我们也来尝试下抽象
熟悉 Java 的都知道, Java 里面有两个内置的配置集合, 就是 System.getenv()和 System.getProperties().
它们分别是系统环境和系统属性, 如下图 01:
一个是 Map 类型, 一个是 Properties 类型, 说明它们都是一些 key-value 形式的值.
而且 Properties 类型是 Java 里的标准配置方式, 它就对应于 *.properties 文件.
至此, 我们已经发现两个问题:
1)配置项都是以 key-value 形式存在的.
2)配置项的来源是多样化的, 如现在的系统环境, 系统属性, 配置文件等, 后期还可能会有其它.
对于配置项的多来源问题, 有以下两种方式解决:
1)可以把所有来源都暴露给用户, 这样使用起来更加精细, 但是也会带来困扰, 可能用户也会迷糊到底该去哪个来源取值.
2)在所有来源前面加一个 "门面", 只把它暴露出去, 用户看到的只是 "单一来源", 就从这里取值, 其它的啥也不用知道.
Spring 选择的是第二种方案, 拿到 key 后, 只需依次去每个来源中查找, 这时只需规定下多个来源之间的优先级顺序即可.
整体可以用一个图形表示, 如下图 02:
这整体也是一种封装变化的思想, 底层的多来源问题被封装起来, 对用户不可见.
最终用户传过来一个 key, 我给返回一个和它对应的 value 就行了.
来来来, 认识两个朋友
配置项在 Java 中通常叫做属性, 即 Property. 每一个来源其实就是一个源泉, 即 Source.
所以在 Spring 中就用 PropertySource 类来表示一个来源, 如下图 03:
注意两个字段, name 和 source,name 就是为每个源起个名字. source 表示真正的资源, 是能从中取出 value 的东西.
然后需要一个门面把多个来源封装起来, 如下图 04:
可以看到它里面有一个来源的列表. 这就是封装. 而且还是有顺序的.
根据 key 取值就依次遍历所有源即可, 如下图 05:
如果所有源中都没找到, 返回 null 就行了.
这样配置项 (或配置属性) 的问题就已经解决了, 很简单吧.
除了配置属性外, 还有 Profile
配置属性是一个很泛化的概念, 说白了它就表示以非写代码的方式从外界向程序中传递特定的值.
它的好处就是修改起来很容易, 只需修改下配置文件或命令行参数, 然后最多重启一下就可以了.
不用修改代码, 自然不用重新编译, 当然也不用重新打包发布.
泛化其实就表示囊括所有的意思, 但是总会有一些特殊情形, 值得单独拿出来特别对待.
如每个软件都会至少经历开发, 测试, 上线这三个阶段, 同样也会有三套环境, 即开发环境, 测试环境, 生产环境.
这里的 "环境" 其实就是一个特殊情况, 我们把它单独拿出来, 就叫做 Profile.
对不同的环境设置不同的 Profile, 程序中可以读取到 Profile, 这样程序就可以适应不同的 Profile, 展示不同的特性.
最终就可以一套代码打天下. 就像华为的一套操作系统适应所有的终端设备. 就像 Java 的一份字节码运行在不同的操作系统上.
在不指定 Profile 时, 通常应该有一个默认的 Profile. 就像汽车默认是运行在城市道路上一样.
在 Spring 中, 默认的 Profile 就叫做 default. 如下图 06:
这个 default 可能没有什么意义, 所以 Spring 提供了修改它的机会, 如下图 07:
可以使用图中的参数名称去指定默认的 Profile, 以符合自己的使用习惯.
比如对于汽车这种情况, 可以这样:
spring.profiles.default=city
我们也可以为不同的环境激活不同的 Profile,Spring 也提供了方法, 如下图 08:
比如汽车上了高速, 我们想狂野一下, 可以激活运动模式:
spring.profiles.active=sports
最后要说的就是, 这个 Profile 可以指定多个, 用逗号分隔即可.
因为 Spring 是用集合存储的, 所以支持多个, 如下图 09:
程序在判断哪些 Profile 被激活时, 可以使用逻辑表达式, 这样就更加灵活了.
支持与, 或, 非, 括号, 如下图 10:
比如我们让程序运行在单节点 debug 模式, 可以这样设置:
spring.profiles.active=standalone,debug
那么下面这些判断将都返回 true:
- standalone -> true
- debug -> true
- standalone | debug -> true
- standalone & debug -> true
- !other -> true
- !unknown -> true
下面这些将都返回 false:
- !standalone -> false
- !debug -> false
- standalone & other -> false
- debug & unknown -> false
- (standalone & other) | (debug & unknown) -> false
- (standalone | debug) & other & unknown -> false
注: 当同时出现与 (&), 或(|) 时, 一定要使用括号.
那什么是 Environment 呢?
很简单, 就是这个公式:
Environment = Properties + Profiles
表示 Properties 的接口, 主要就是处理一些 key-value, 如下图 11:
Environment 继承了这个接口, 又加入处理 Profile 的内容, 如下图 12:
由于要支持 key-value 数据类型的转换和 ${..}表达式的解析, 所以需要能够配置, 如下图 13:
由于需要能够以编程的方式激活 Profile 或设置默认 Profile, 所以也需要能够配置, 如下图 14:
所以, 这四个接口就是 Spring 环境的全部了.
在 SpringBoot 中 Environment 的真面目
下面是非 web 环境:
- StandardEnvironment {
- activeProfiles=[], defaultProfiles=[default],
- propertySources=[
- ConfigurationPropertySourcesPropertySource {
- name='configurationProperties'
- },
- SimpleCommandLinePropertySource {
- name='commandLineArgs'
- },
- PropertiesPropertySource {
- name='systemProperties'
- },
- OriginAwareSystemEnvironmentPropertySource {
- name='systemEnvironment'
- },
- RandomValuePropertySource {
- name='random'
- },
- OriginTrackedMapPropertySource {
- name='applicationConfig: [classpath:/application.yml]'
- },
- ResourcePropertySource {
- name='class path resource [mode.properties]'
- },
- ResourcePropertySource {
- name='class path resource [greeting.properties]'
- }]
- }
可以看到配置属性有多个来源, 包括命令行参数, 系统属性, 系统环境, 随机数, YAML 配置文件, properties 配置文件等.
以 -- 开头的参数会出现在命令行参数这个源里, 如下图 15:
以 - D 开头的参数会出现在系统属性这个源里, 如下图 16:
这些源在上面的顺序就是它们的优先级, 可见命令行的最高, properties 文件的最低.
注意源中的第一个, 即名称为 configurationProperties 的, 主要是为了适应 SpringBoot 的属性名的 "松散" 绑定而专门用来处理属性名称的.
它并不真正提供属性值, 它的值来源于除它之外的其它源.
如果不明白什么是属性名的松散绑定的, 看这个示例:
user-name, user_name, userName
这三个属性名称都可以绑定到一个类的 userName 属性上.
下面是基于 Servlet 的 Web 环境:
- StandardServletEnvironment {
- activeProfiles=[], defaultProfiles=[default],
- propertySources=[
- ConfigurationPropertySourcesPropertySource {
- name='configurationProperties'
- },
- SimpleCommandLinePropertySource {
- name='commandLineArgs'
- },
- StubPropertySource {
- name='servletConfigInitParams'
- },
- ServletContextPropertySource {
- name='servletContextInitParams'
- },
- PropertiesPropertySource {
- name='systemProperties'
- },
- OriginAwareSystemEnvironmentPropertySource {
- name='systemEnvironment'
- },
- RandomValuePropertySource {
- name='random'
- },
- OriginTrackedMapPropertySource {
- name='applicationConfig: [classpath:/application.yml]'
- },
- ResourcePropertySource {
- name='class path resource [mode.properties]'
- },
- ResourcePropertySource {
- name='class path resource [greeting.properties]'
- }]
- }
和上面的唯一区别就是多了两个和 Web 相关的源, 就是 ServletConfig 和 ServletContext.
可以从它们两个里面取出初始化参数, 而且它们的优先级仅次于命令行参数.
备注: 还有一种基于 Reactive(响应式)的 Web 环境. 暂时先不讨论了.
每一个源里面其实都是 key-value, 内容较多, 不再展示. 可以自己运行下试试.
本文示例代码:
https://github.com/coding-new-talking/playing-spring.git
>>> 玩转 SpringBoot 系列文章 <<<[玩转 SpringBoot] 配置文件 YAML 的正确打开姿势
[玩转 SpringBoot] 用好条件相关注解, 开启自动配置之门
[玩转 SpringBoot] 给自动配置来个整体大揭秘
>>> 品 Spring 系列文章 <<<品 Spring: 帝国的基石
品 Spring:bean 定义上梁山
品 Spring: 实现 bean 定义时采用的 "先进生产力"
品 Spring: 注解终于 "成功上位"
品 Spring: 能工巧匠们对注解的 "加持"
品 Spring:SpringBoot 和 Spring 到底有没有本质的不同?
品 Spring: 负责 bean 定义注册的两个 "排头兵"
品 Spring:SpringBoot 轻松取胜 bean 定义注册的 "第一阶段"
品 Spring:SpringBoot 发起 bean 定义注册的 "二次攻坚战"
品 Spring: 注解之王 @Configuration 和它的一众 "小弟们"
品 Spring:bean 工厂后处理器的调用规则
品 Spring: 详细解说 bean 后处理器
品 Spring: 对 @PostConstruct 和 @PreDestroy 注解的处理方法
品 Spring: 对 @Resource 注解的处理方法
品 Spring: 对 @Autowired 和 @Value 注解的处理方法
品 Spring: 真没想到, 三十步才能完成一个 bean 实例的创建
品 Spring: 关于 @Scheduled 定时任务的思考与探索, 结果尴尬了
>>> 热门文章集锦 <<<
毕业 10 年, 我有话说
[面试] 我是如何面试别人 List 相关知识的, 深度有点长文
我是如何在毕业不久只用 1 年就升为开发组长的
爸爸又给 Spring MVC 生了个弟弟叫 Spring WebFlux
[面试] 我是如何在面试别人 Spring 事务时 "套路" 对方的
[面试] Spring 事务面试考点吐血整理(建议珍藏)
[面试] 我是如何在面试别人 Redis 相关知识时 "软怼" 他的
[面试] 吃透了这些 Redis 知识点, 面试官一定觉得你很 NB(干货 | 建议珍藏)
[面试] 如果你这样回答 "什么是线程安全", 面试官都会对你刮目相看(建议珍藏)
[面试] 迄今为止把同步 / 异步 / 阻塞 / 非阻塞 / BIO/NIO/AIO 讲的这么清楚的好文章(快快珍藏)
[面试] 一篇文章帮你彻底搞清楚 "I/O 多路复用" 和 "异步 I/O" 的前世今生(深度好文, 建议珍藏)
[面试] 如果把线程当作一个人来对待, 所有问题都瞬间明白了
Java 多线程通关 --- 基础知识挑战
品 Spring: 帝国的基石
作者是工作超过 10 年的码农, 现在任架构师. 喜欢研究技术, 崇尚简单快乐. 追求以通俗易懂的语言解说技术, 希望所有的读者都能看懂并记住. 下面是公众号的二维码, 欢迎关注!
来源: https://www.cnblogs.com/lixinjie/p/playing-springboot-004.html