码匠笔记

用心雕琢

安全的使用Maven实现不同环境配置文件的部署

访问

本文内容概要如下:

1, Maven Filter介绍和用法
2, Maven Profile介绍和用法
3, 使用Maven的Filter和Profile实现项目的多环境部署
4, Maven的Profile优先级源码分析

前言

每一个项目都有许多部署环境,诸如开发环境,测试环境,沙盒环境,线上环境,有的项目还会更多。每一个环境都有自己独有的配置文件,比如数据库连接地址,静态资源的访问地址等等。那么如何优雅的分离这些配置文件是首要任务。目前已有的技术可以轻松的搞定这件事情,比如 Spring 的 Profile,Maven 的 Profile,但是有的时候我们为了方便,直接把这些配置文件都放在了Project里面,这样增加了项目的风险,也不便于管理。那么接下来我们就想来一个优雅的方法使用 Maven Profile 安全分离配置文件。

Filter

Maven Filter可以支持将写到 settings.xml, pom.xml, 或是自定义 *.properties 文件里面的 propertiesbuild 的时候自动替换指定目录配置文件里面的占位符,以实现动态指定配置。基本配置如下:
我们在resources 目录有一配置文件 src/main/resources/config.properties 包含如下内容(如果使用Spring-boot需要把 ${username} 替换为 @username@)

src/main/resources/config.properties
1
GitHub : ${username}

POM文件如下,用来指定资源的存放路径和是否使用filter

${project}/pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.github.codedrinker</groupId>
    <artifactId>maven-env-deploy</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>My Resources Plugin Practice Project</name>

    <properties>
        <username>codedrinker</username>
    </properties>
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering><!--至关重要-->
            </resource>
        </resources>
    </build>
</project>

运行命令

1
2
mvn resources:resources
#这里同样可以使用 mvn clean compile,只是resources不会编译代码,只会构建资源文件,这样更方便我们调试

target/classes/config.properties 查看文件内容,已经变化了。

target/classes/config.properties
1
GitHub : codedrinker

Profile

Profile 可以让我们根据不同的环境,定义不同的 properties。配置如下:

${project}/pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.github.codedrinker</groupId>
    <artifactId>maven-env-deploy</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>My Resources Plugin Practice Project</name>

    <profiles>
        <profile>
            <id>sandbox</id>
            <properties>
                <username>codedrinker</username>
            </properties>
        </profile>
        <profile>
            <id>dev</id>
            <properties>
                <username>majiang</username>
            </properties>
        </profile>
    </profiles>
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering><!--至关重要-->
            </resource>
        </resources>
    </build>
</project>

运行

1
mvn resources:resources -P sandbox

结果如下

1
GitHub : codedrinker

运行

1
mvn resources:resources -P dev

结果如下

1
GitHub : majiang

这样就实现了在不同环境部署不同的配置文件了。但是接下来问题来了,这样如果我们存储了用户名密码等信息,也会随着项目一起提交。这时候 Mavensettings.xml 就派上用场了。

Settings.xml

正好 settings.xml 可以包含profiles,那么我们直接把对应的配置放进来不就可以了吗?有两个地方包含settings.xml

  • Maven的安装目录 ${maven.home}/conf/settings.xml
  • 用户的目录 ${user.home}/.m2/settings.xml

前者是全局的设置,后者是用户的设置,官方文档中指出如果两个都有设置,会以用户为主进行合并。但是我并不清楚它的合并力度,于是做了如下实验

系统配置

${maven.home}/conf/settings.xml
1
2
3
4
5
6
7
8
9
<!-- 系统配置 -->
<profile>
    <id>sandbox</id>
    <properties>
        <username>majiang</username>
        <password>majiang</password>
        <website>http://www.majiang.life</website>
    </properties>
</profile>

用户配置

${user.home}/.m2/settings.xml
1
2
3
4
5
6
7
8
<!-- 用户配置 -->
<profile>
        <id>sandbox</id>
        <properties>
            <username>codedrinker</username>
            <password>codedrinker</password>
        </properties>
</profile>

config.properties

src/main/resources/config.properties
1
2
3
username : ${username}
password : ${password}
website : ${website}

再次运行命令以后的结果

target/classes/config.properties
1
2
3
username : codedrinker
password : password
website : ${website}

令人奇怪的事情发生了,编译的结果确实以用户级settings.xml为准,但是系统级settings.xml里面额外配置的website并没有编译。于是我就带着这个疑问看了一下Maven的源码: 本身 Maven 定义了一个 Profile 类用来映射每一个profile,该类有一个properties属性,用来存放当前profile的配置:

1
2
3
4
/**
 * Field properties.
 */
private java.util.Properties properties;

在读取配置文件的时候,直接读取properties,没有其他处理

SettingsXpp3Reader.java
1
2
3
4
5
6
7
8
9
else if ( checkFieldWithDuplicate( parser, "properties", null, parsed ) )
            {
                while ( parser.nextTag() == XmlPullParser.START_TAG )
                {
                    String key = parser.getName();
                    String value = parser.nextText().trim();
                    profile.addProperty( key, value );
                }
            }

关键在于以用户的settings为主,合并配置的逻辑,他的逻辑是如果profile.id不相同才会合并到用户级别的settings,不会深度的比较。于是这个问题是无解的,他所说的以用户级settings.xml为主指的是每一个配置,里面的每一个property是不被合并的。

MavenSettingsMerger.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static <T extends IdentifiableBase> void shallowMergeById( List<T> dominant, List<T> recessive,
                                                                       String recessiveSourceLevel )
    {
        Map<String, T> dominantById = mapById( dominant );

        for ( T identifiable : recessive )
        {
            if ( !dominantById.containsKey( identifiable.getId() ) )
            {
                identifiable.setSourceLevel( recessiveSourceLevel );

                dominant.add( identifiable );
            }
        }
    }

所以我们想同时使用系统和用户级别的settings的时候,需要格外注意这一点。

POM.xml

文档中并没有明确说明项目中的pom.xml文件和系统用户级别的settings.xml的优先级,但是经测试写在项目里面的配置文件,会直接编译到指定的config.properties里面,并且他会把独有的属性也编译到文件。

总结

所以最终的结果

  • 非安全性的配置文件,直接配置到项目的pom.xml里面。
  • 安全性质的配置文件,配置到用户级别的settings.xml
  • 系统级别的settings.xml做一些系统级别的配置,不轻易使用,和用户区分开来。

参考链接

Filter
Profile
howto-properties-and-configuration
Settings.xml

扩展阅读

作者

本文作者麻酱,欢迎讨论,指正和转载,转载请注明出处。
原文地址:安全的使用Maven实现不同环境配置文件的部署
如果兴趣可以关注作者微信订阅号:码匠笔记
majiangbiji

评论