编程

清理 Spring Properties 文件

5 2025-11-05 13:56:00

1. 概述

在为 Spring 项目创建配置属性时,我们可能会选择将它们拆分到多个文件中。Spring 配置文件中有不同的属性是很常见的

随着时间的推移,由于属性数量众多,这些文件可能会包含大量重复内容,难以阅读。清理这些文件需要大量的手动工作。

本文中,我们将介绍一个名为 Spring Properties Cleaner 的 Maven 插件,它可以帮助你整理并控制配置。

2. 示例

2.1. Properties 文件

假设我们有两个配置文件——devprod。在这个例子中,我们还没有创建一个简单的 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_hostdev 配置文件中出现了两次
  • 有时,我们的键的格式是 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>

这里我们添加了一个 clusteredsort。我们本来可以选择 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>

此正则表达式可识别 httpshttp 协议。如果其他键也存在此类问题,我们可以进一步自定义它。

让我们看看它如何影响我们的 applicaton-prod.properties

我们可以看到 http scheme 现在是 upstream.hostredis_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,我们既可以整理属性文件,又可以防止问题再次发生。