Maven构建项目时,src/main/resources目录下的文件默认会被原样复制到classpath中,但实际开发中我们经常希望配置文件里的版本号、构建时间、数据库地址等信息在打包时自动替换。这个需求就要靠resource filtering来实现。不少人对这套配置一知半解,导致打包后配置文件出现占位符没被替换、图片被过滤器破坏等问题。本文从原理到实战,把pom.xml中资源过滤的配置方法讲透。

资源过滤的工作原理
Maven处理资源文件的组件叫maven-resources-plugin,它在process-resources阶段把src/main/resources下的文件复制到target/classes目录。如果某个资源开启了filtering,复制过程就会多一步文本替换:Maven会扫描文件内容,把所有${...}形式的占位符替换成对应的属性值。这些属性值的来源包括pom.xml中的properties节点、settings.xml中的属性、系统属性以及环境变量。
理解这一点很关键:filtering本质上是字符串替换,Maven并不关心文件类型。这意味着如果你对一个二进制文件开启了过滤,Maven会把它当文本读入再写出,编码转换过程可能破坏文件内容。这也是为什么默认配置下filtering是关闭的,需要显式声明。
占位符替换的优先级从高到低大致是:命令行传入的属性(如mvn -Djdbc.url=xxx)、profile中定义的属性、pom.xml的properties属性、settings.xml属性、系统属性和环境变量。掌握这个顺序有助于排查属性被意外覆盖的问题。
pom.xml中的基础配置写法
先看一个最小可用的配置。在build节点下声明resources,指定哪些目录需要处理、哪些文件开启过滤:
<build>
<resources>
<!-- 第一组:配置文件开启过滤,占位符会被替换 -->
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>*.properties</include>
<include>*.yml</include>
<include>*.xml</include>
</includes>
</resource>
<!-- 第二组:二进制文件关闭过滤,原样复制 -->
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>*.properties</exclude>
<exclude>*.yml</exclude>
<exclude>*.xml</exclude>
</excludes>
</resource>
</resources>
</build>这里有一个非常容易踩的坑:一旦你在pom.xml中显式声明了resources节点,Maven的默认资源处理规则就被完全覆盖了。如果只写了第一组配置,那么properties、yml、xml以外的文件(比如图片、字体)都不会被复制到target/classes,运行时会报找不到资源的错误。所以上面的第二组配置不是可有可无的,它负责把剩下的文件原样拷贝进去。
两个resource节点匹配同一个目录并不冲突,Maven会依次处理,按includes和excludes的匹配结果决定每个文件走哪条规则。文件匹配支持通配符,*.properties匹配一级目录,**/*.properties匹配任意层级子目录,写的时候要注意这个差别。
自定义属性与占位符替换实战
定义了过滤规则之后,还需要提供属性来源。最常见的做法是在pom.xml的properties节点中定义变量,然后在配置文件中用占位符引用。假设src/main/resources下有一个jdbc.properties,内容如下:
jdbc.url=${jdbc.url}
jdbc.username=${jdbc.username}
jdbc.password=${jdbc.password}在pom.xml中补充属性定义:
<properties>
<jdbc.url>jdbc:mysql://127.0.0.1:3306/demo</jdbc.url>
<jdbc.username>root</jdbc.password>
<jdbc.password>123456</jdbc.password>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>执行mvn clean package后,打开target/classes下的jdbc.properties,就能看到占位符已经被替换成实际值。需要注意的是${project.version}、${project.artifactId}这类内置属性可以直接使用,不用额外定义。另外建议始终设置project.build.sourceEncoding为UTF-8,否则中文注释在过滤替换后可能变成乱码。
还有一个细节:如果某个属性没有定义,Maven默认会让占位符原样保留在文件中,并不会报错。这既是好事也是隐患,好处是不影响构建,坏处是问题被延迟到运行时才暴露。可以在构建日志中搜索相关文件名确认过滤是否执行,也可以直接查看target/classes下的产物验证替换结果。
结合profile实现多环境打包
资源过滤真正的价值在多环境场景。开发、测试、生产三个环境的数据库地址不同,不可能维护三份配置文件手动切换。配合maven profile可以做到按需替换:
<profiles>
<profile>
<id>dev</id>
<properties>
<jdbc.url>jdbc:mysql://127.0.0.1:3306/dev_db</jdbc.url>
<env.label>开发环境</env.label>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>prod</id>
<properties>
<jdbc.url>jdbc:mysql://10.0.1.100:3306/prod_db</jdbc.url>
<env.label>生产环境</env.label>
</properties>
</profile>
</profiles>打包时通过mvn package -P prod激活生产环境的profile,配置文件中的占位符就会替换成生产环境的值。如果配合spring-boot-maven-plugin使用,还可以结合@...@分隔符避免和Spring自身的${...}占位符冲突,只需在resource节点中配置delimiters即可。
最后提醒一点:Spring Boot项目从2.x开始,其父pom已经默认对application.properties和application.yml开启了过滤,且分隔符是@符号。如果你在自己的pom里再叠加一套${}过滤规则,容易出现双重替换或者互相干扰,建议先查看有效pom(mvn help:effective-pom)确认已有的资源配置,再决定如何追加自己的规则,避免重复声明带来的奇怪问题。
Maven资源过滤pom.xml配置resource filtering修改时间:2026-09-11 02:46:30