清理 Spring Properties 文件
1. 概述
在为 Spring 项目创建配置属性时,我们可能会选择将它们拆分到多个文件中。Spring 配置文件中有不同的属性是很常见的。
随着时间的推移,由于属性数量众多,这些文件可能会包含大量重复内容,难以阅读。清理这些文件需要大量的手动工作。
本文中,我们将介绍一个名为 Spring Properties Cleaner 的 Maven 插件,它可以帮助你整理并控制配置。
2. 示例
2.1. Properties 文件
假设我们有两个配置文件——dev 和 prod。在这个例子中,我们还没有创建一个简单的 application.properties 文件。我们的 application-dev.properties 文件包含一组设置:
spring.datasource.url=jdbc:postgresql://${db_server}/mydatabase
spring.datasource.username=${USERNAME}
spring.datasource.password = ${PASSWORD}
redis_host=localhost
spring.redis.host=http://${redis_host}
spring.redis.port=6379
redis_host=localhost
spring.jpa.show-sql=true
upstream.host = myapp.dev.myorg.com
# upstream services
upstream.service.users.url=http://${upstream.host}/api/users
upstream.service.products.url=http://${upstream.host}/api/products
spring.redis.timeout=10000而 application-prod.properties 文件有一套不同的设置:
spring.datasource.url=jdbc:postgresql://${db_server}/mydatabase
spring.datasource.username=${USERNAME}
spring.datasource.password = ${PASSWORD}
# upstream services
upstream.service.users.url=https://${upstream.host}/api/users
upstream.service.products.url=https://${upstream.host}/api/products
redis_host=azure.redis6a5d54.microsoft.com
spring.redis.host=https://${redis_host}
spring.redis.port=6379
upstream.host = myapp.prod.myorg.com
spring.redis.timeout=2000后文中,我们将清理这些文件。
2.2. Properties 文件有什么问题?
让我们回顾一下上述文件中的问题:
- 键
redis_host在dev配置文件中出现了两次 - 有时,我们的键的格式是
name=value,有时则有多余的空格——name = value - 属性没有按任何逻辑顺序排序,多个
spring.redis键位于文件的不同位置 - “upstream 服务”的 URL 列表在两个配置中基本相同,但由于
dev端是http,而prod端是https,因此无法共享 spring.datasource属性在两个文件中似乎相同,可能可以纳入到一个通用的application.properties文件
即使这些文件很短,也有很多细节需要考虑并尝试改进。
让我们使用 Spring Properties Cleaner 自动化执行此操作。然后,让我们将该插件用作 linter,以确保它不会再次陷入这种状态。
3.将 Spring Properties Cleaner 添加到我们的构建中
Spring Properties Cleaner 是一个 Maven 插件。如果它检测到属性文件中的任何问题,构建就会失败。
我们首先将最新版本的插件添加到 pom.xml 中:
<plugin>
<groupId>uk.org.webcompere</groupId>
<artifactId>spring-properties-cleaner-plugin</artifactId>
<version>1.0.6</version>
<executions>
<execution>
<goals>
<goal>scan</goal>
</goals>
</execution>
</executions>
</plugin>现在,如果我们运行 Maven compile,这个插件将会报错:
$ mvn compile
...
[INFO] --- spring-properties-cleaner:1.0.3:scan (default) @ spring-properties-cleaner ---
[INFO] Executing scan on /Users/ashleyfrieze/dev/tutorials/maven-modules/maven-plugins/spring-properties-cleaner/src/main/resources
[ERROR] application-dev.properties: redis_host has duplicate values L5:'localhost',L10:'localhost'
[ERROR] File 'application-dev.properties' does not meet standard - have you run fix?
[ERROR] File 'application-prod.properties' does not meet standard - have you run fix?
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE使用默认配置,该插件可以检测轻微的格式错误和重复的键。
4. 修复重复和格式
现在让我们运行 fix 操作来修复这些小错误:
$ mvn spring-properties-cleaner:fix然后,让我们使用版本控制软件检查 application-dev.properties 的差异:

渐进式修复这些文件是个好主意,并在过程中将更改签入版本控制系统。
使用此默认配置,我们可以看到文件开头和下方的一些空格已被移除。此外,我们还可以看到重复属性已被移除。
现在插件已经整理好了文件,除非我们意外地更改了这些文件并引发了更多问题,否则我们的 Maven 构建将再次成功。
但是,我们可以配置插件来控制文件的更多方面。
5. 属性排序
接下来,让我们将排序配置添加到该插件中:
<plugin>
...
<artifactId>spring-properties-cleaner-plugin</artifactId>
...
<configuration>
<sort>clustered</sort>
</configuration>
</plugin>这里我们添加了一个 clustered 的 sort。我们本来可以选择 sorted,它会按字母顺序排列键(将数字视为数字序列)。而 clustered 会遵循文件中键的原始顺序,只会移动与相同前缀的键不同的键。
让我们看看重新运行 fix 命令后 application-prod.properties 中发生了哪些变化:

该排序操作将 Spring 的所有键合并到一起,并将 upstream.host 键提升到 redis_host 键之上,使 redis_host 位于文件底部。
检查完所有文件的差异后,我们可以将它们签入源代码管理,并查看其他改进。
6. 内联前缀
如果我们的目标是重构文件,使通用内容包含在 application.properties 中,那么我们可能需要处理这样的情况:两个文件的值基本相同,但占位符不完整导致难以找到通用模式。
让我们再次看看示例文件是如何表示 URL 的:
- 在开发环境中:
upstream.service.users.url=http://${upstream.host}/api/users
在生产环境中:upstream.service.users.url=https://${upstream.host}/api/users
如果 upstream.host 占位符也可以包含 scheme 协议,那么这两个值将相同。
这时就需要使用 inlinePrefix 配置了。它接受一个正则表达式,并修改占位符以内联重复的前缀:
<configuration>
<inlinePrefix>https?://</inlinePrefix>
</configuration>此正则表达式可识别 https 和 http 协议。如果其他键也存在此类问题,我们可以进一步自定义它。
让我们看看它如何影响我们的 applicaton-prod.properties:

我们可以看到 http scheme 现在是 upstream.host 和 redis_host 键的一部分。这将使我们能够将更多通用性属性到 application.properties 中。
让我们检查此文件并继续。
7. 提取到通用属性文件中
要让插件提取属性,我们添加了 common 配置:
<configuration>
...
<common>full</common>
</configuration>7.1. 使用 full 提取通用文件
common 提取有三种可能的模式:full 模式、consistent 模式和 multiple 模式。在 full 模式下,属性必须在所有属性文件中保持一致才能提升到 application.properties 文件。
其他模式(consistent 模式和 multiple 模式)允许将出现在特定位置的属性添加到通用文件中。这可能会产生副作用,因此需要进行更多检查。
让我们看看 full 模式对我们的文件做了什么。
它创建了一个新的 application.properties 文件:
spring.datasource.url=jdbc:postgresql://${db_server}/mydatabase
spring.datasource.username=${USERNAME}
spring.datasource.password=${PASSWORD}
spring.redis.host=${redis_host}
spring.redis.port=6379
# upstream services
upstream.service.users.url=${upstream.host}/api/users
upstream.service.products.url=${upstream.host}/api/products它包含了其他文件中的大部分内容,并且采用了通用格式。
我们的 application-dev.properties 现在小了很多:
spring.redis.timeout=10000
spring.jpa.show-sql=true
redis_host=http://localhost
upstream.host=http://myapp.dev.myorg.com我们的 application-prod.properties 也同样简化了。
剩下的唯一能使代码更简洁的方法就是删除键值之间多余的换行符。
7.2. 其他通用文件模式
在我们的示例中,我们使用了 full 模式,这是最安全的选项,因为只有当属性在所有特定于配置文件的文件中都相同时,它才会将属性提取到通用的 application.properties 文件中。
使用 consistent 模式,我们可以提取一个属性,该属性在所有找到它的文件中都相同,但并非在所有文件中都存在。这会产生副作用,即会将我们可能希望从某些配置文件中排除的键提升到通用文件中。
在 multiple 模式下,如果某个属性出现在多个属性文件中,并且具有最常见的值,则会将其提取到通用文件中。假设我们在两个属性文件中将 spring.redis.timeout 设置为 10000,在第三个属性文件中设置为 20000。在此模式下,10000 的值将位于通用的 application.properties 文件中,而具有异常值的文件将保留其自定义值。
当我们开始在文件之间移动不一致的属性时,可能会出现某些属性变成通用属性的风险,以至于我们需要在特定于配置文件的文件中覆盖它们。这些更改需要更仔细的检查和测试。
8. 清理垂直空白
我们可以使用 remove 删除所有垂直空格,不过我们可以使用 section 设置在具有不同前缀的文件中块之间放置空格:
<configuration>
...
<whitespace>section</whitespace>
</configuration>当我们在此最后一次运行 fix 命令后,空格被清理了:

现在,我们的属性文件已经干净整洁了。
9. 小结
本文中,我们探讨了随着属性文件变大,一些可能造成混乱的情况。
我们安装了 Spring Properties Cleaner Maven 插件,并用它来监控构建过程中的问题。然后,我们增强了它的配置,选择了用于排序和简化文件的选项,最后将属性提取到一个集中的 application.properties 文件中。
通过将此插件用作 linter,我们既可以整理属性文件,又可以防止问题再次发生。