☰
Maven资源文件配置:一文讲透resources与testResources标签
2026/10/20 12:12:30 网站建设 项目流程

打开任何一个中型项目的pom.xml,如果你翻到 这一段,大概率会在里面看到 和 这几个标签。很多刚接触Maven的人对着这三个标签一脸懵:build好理解,就是构建配置;resources和testResources到底是干嘛的?不写行不行?写了之后和默认行为有什么区别?

这篇教程就把这三个标签一次讲透。我会从Maven的构建生命周期说起,讲清楚resources和testResources的默认行为、自定义写法、过滤机制、实战配置,最后再分享几个我在真实项目里踩过和排查过的资源文件问题。适合刚学Maven、准备正经管理项目资源文件的人,也适合那种“项目跑得好好的但资源总是莫名其妙丢文件”的老兄。

1. build、resources、testResources:先分清它们在pom.xml里的位次

1.1 三个标签的管辖范围

先说位次关系,这个搞错了后面全是坑。

是构建配置的顶层容器,所有跟“构建过程”有关的配置都住在它里面,比如最终产物名finalName、插件plugins、输出目录directory、以及我们这篇的主角resources和testResources。

是 的子元素,管的是主代码目录src/main/resources下那些资源文件,处理完会进入target/classes,最终被装进jar包或者war包。

同样也是 的子元素,管的是测试代码目录src/test/resources下的资源文件,处理完会进入target/test-classes,只在测试阶段生效,不会进入最终产物。

可以这样记:resources和testResources是build里的两个“资源处理区”,一个负责生产阶段的资源,一个负责测试阶段的资源。长得很像,但互相独立,配置规则也各自分开。

1.2 构建生命周期中它们什么时候被触发

Maven把一次构建拆成了validate、compile、test、package、install、deploy等多个阶段。资源文件不会自己长腿跑进target目录,它靠的是maven-resources-plugin这个插件。这个插件有两个核心目标:

  • resources:resources,绑定在process-resources阶段,处理主资源
  • resources:testResources,绑定在process-test-resources阶段,处理测试资源

注意process-resources阶段在compile之前,也就是说资源处理比代码编译更早。这一点很重要,比如你用了lombok或者代码里要读取classpath下的配置文件,这些文件必须已经在target/classes里,编译期的注解处理器和资源加载才能正常工作。

所以你在pom里写的 和 ,本质上是给maven-resources-plugin传递配置参数:处理哪些目录、是否过滤、过滤的分隔符是什么、包含哪些文件、排除哪些文件。如果什么都不写,插件会用默认规则去捞src/main/resources和src/test/resources。

2. resources标签:从默认行为到自定义配置的全面拆解

2.1 默认资源目录,可能不是你想象的那样

Maven的默认行为是这样的:

  • 主资源目录固定为${basedir}/src/main/resources,构建时原样复制到target/classes,最终打包后位于jar包根路径下
  • 测试资源目录固定为${basedir}/src/test/resources,构建时复制到target/test-classes

如果你用的是标准目录结构,pom里什么都不写,IDE也能正常识别,打包也不会漏文件,这就是大多数人最初接触Maven时的状态。

但项目一复杂,默认行为就不太够用了。举几个常见场景:

  • 公司统一要求把配置文件放在src/main/conf目录,不放在src/main/resources里
  • 不同环境部署,希望配置文件里的环境占位符在打包时自动替换
  • 资源目录里混了个别人误放的大文件,希望排除掉,不让它进jar包
  • 某些资源文件需要指定不同的编码处理,避免中文乱码

这时候你才需要动resources标签。但这里有一个非常反直觉的规则:一旦你在pom里显式写了 ,默认的src/main/resources不会被额外保留。你会认为是“新增”,实际上Maven理解为“全部重定义”。新手看网上的配置照着抄,把默认目录丢了,还以为是CVS或文件上传出问题了。

2.2 自定义资源目录的标准写法

直接看一个最常见的写法,我把每一行都解释一遍:

<build> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> <resource> <directory>src/main/conf</directory> <filtering>false</filtering> </resource> </resources> </build>

resource子标签表示一个资源入口,每个入口可以理解成一条“取货传送带”。directory填写资源目录,路径写相对路径,默认相对项目根目录。filtering表示是否对该目录下的文件做占位符替换,默认false。

多个resource同时存在时,按声明顺序依次处理。如果两个声明处理的是同一目标位置,后声明的会覆盖先声明的。所以多个资源目录尽量保证彼此不重叠,尤其不要让targetPath指向同一个目录。

还有一点,directory是可以不存在的。Maven不会因为你写了一个不存在的资源目录就报错,它会静默跳过。这个特性容易被误解成“配置没生效”,排查半天发现只是目录拼写错误,实际处理的是另一个目录。

2.3 filtering与delimiter:让配置文件活起来

filtering是resources标签里最有价值、也最容易出事的功能。它做的事情是:处理文件内容时,把形如${key}的占位符替换成pom.xml里properties标签定义的值,或者外部属性文件里的值。

典型场景:同一个项目要部署到测试环境和生产环境,项目里的application.properties写的是数据库连接、Redis地址、日志路径。如果每个环境都手动改一次文件,要么容易漏,要么容易把只属于本机的地址推到远程仓库。用filtering可以在打包阶段完成替换。

要开启过滤,filtering必须显式写成true。替换时默认的分隔符是${...},如果你觉得这个分隔符和别的模板冲突,可以自定义:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <configuration> <delimiter>@</delimiter> </configuration> </plugin>

这样占位符就变成了@key@。有些工程内部规范使用@xxx@作为占位符,就是这个原因。

这里要强调一个Feature和Bug之间的微妙区域:如果pom的properties里没有定义某个key,filtering处理时不会直接报错,而是可能保留原样的${key},也可能替换成空字符串,具体行为取决于插件版本。这就导致运行时配置文件里出现空值,异常信息指向数据库地址为空,排查时又不好定位。所以凡是开了filtering的资源文件,打包后务必检查target/classes里的实际内容,别等运行时报错再回头找。

2.4 includes和excludes:按需筛选资源文件

如果资源目录里只想放特定类型的文件,用includes和excludes控制。Maven的通配符规则是:

  • 匹配任意字符但不匹配目录分隔符,所以.xml只匹配当前目录下的xml
  • **匹配任意数量的目录层级
  • 路径分隔符统一用/,不要用反斜杠

一个完整示例:

<resource> <directory>src/main/resources</directory> <filtering>true</filtering> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <excludes> <exclude>**/*.sample</exclude> </excludes> </resource>

规则优先级上,excludes优先于includes。也就是说一个文件即使完全匹配include的规则,只要命中exclude,最后的结果还是被排除。这个顺序经常有人记反,导致某些不该排除的样例配置被过滤掉了。

实践建议:配置粒度要恰到好处。纯文本、可能带占位符的文件,加入过滤范围;图片、证书、字体、Excel模板这类二进制文件,要么单独放目录并保持filtering=false,要么用excludes直接排除,千万别让它们淌filtering这趟浑水。

3. testResources:测试资源里容易翻车的细节

3.1 testResources和resources的真正区别

表面上看,testResources和resources只是目录不同,一个src/test/resources,一个src/main/resources。但实际使用中差异很大:

测试资源里的数据主要有:单元测试用的数据初始化脚本、Mock数据、测试证书、各种测试配置properties。它们的共同点是只服务于测试代码,不会进入最终交付的jar包。

一个关键的知识点是classpath加载顺序。测试执行时,target/test-classes会排在target/classes前面。也就是说,如果src/test/resources下存在和src/main/resources同名的文件,测试读到的是测试资源目录里的那一份。很多“只在测试环境出现的问题”都是因为这个规则——测试数据目录里遗留了一份旧模板,覆盖了主资源里正确的新模板。

还有一个踩坑点:测试资源同样要复制到target/test-classes才生效,而这个过程不是直接由主资源的配置来管,必须单独配testResources。很多人主资源配了filtering,测试资源没配,测试时调用配置文件仍然得到原样的占位符,卡了半天才意识到两套配置互相独立。

3.2 测试资源过滤的典型用法

测试资源过滤最常见的用途:测试环境要用临时数据库配置,但不同机器上的数据库地址不一样。

比如src/test/resources/database.properties内容是这样:

db.url=${db.url} db.user=${db.user}

pom.xml里定义属性:

<properties> <db.url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</db.url> <db.user>sa</db.user> </properties>

然后配置testResources做过滤:

<testResources> <testResource> <directory>src/test/resources</directory> <filtering>true</filtering> </testResource> </testResources>

这样跑mvn test时,database.properties里的${db.url}会被替换成H2内存数据库的连接串,单元测试能正常拿到配置。如果你在CI流水线里通过-D参数覆盖属性,比如-Ddb.url=jdbc:mysql://ci-host/db,测试环境还能无改动切换数据库。

3.3 testResources不生效时按这个顺序查

如果配置了testResources但测试代码读不到文件,我的排查习惯是固定走这几步:

  1. 确认标签位置。 必须是 下面的直接子元素,不是 的子元素,也不是 的子元素。
  2. 确认目录真实存在。src/test/resources不存在时Maven不报错,安静地跳过处理,你盯着pom看半天也看不出端倪。
  3. 去target/test-classes目录看文件,用命令ls -l target/test-classes确认文件是否真的复制进去了,以及过滤后的内容是不是你期望的值。如果文件压根没复制,问题一定出在pom配置或目录路径上。
  4. 检查有没有残留的旧构建产物,跑mvn clean test排除缓存干扰。
  5. 检查当前生效的profile。如果你在某个profile里定义属性,但执行时没激活这个profile,属性就不会被替换。

这五步能解决九成以上的testResources不生效问题。其中第三步最实用,直接看target目录,比一遍遍猜配置有效得多。

4. 一份可直接抄作业的资源区配置实例

4.1 接近真实项目的示例

下面这份配置我经常在偏向Spring Boot的项目里用,去掉了一堆花哨插件,只保留资源相关的内容:

<build> <finalName>demo-app</finalName> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> <resource> <directory>src/main/scripts</directory> <filtering>false</filtering> <targetPath>script</targetPath> </resource> </resources> <testResources> <testResource> <directory>src/test/resources</directory> <filtering>true</filtering> </testResource> </testResources> </build>

逐段说明:

finalName指定最终构建产物名,打出来的jar包是demo-app.jar,而不是demo-app-1.0.0.jar,团队统一产物名时很有用。

第一个resource把默认的src/main/resources重新声明一遍,filtering=true,这样application.properties里的环境占位符能在打包时替换。

第二个resource引进了src/main/scripts目录,里面放服务启动脚本或运维脚本,filtering=false防止脚本内容被错误替换,targetPath=script让这类文件单独归类在jar包下的script目录,不污染classpath根路径。

testResources配置了过滤,因为测试资源里有数据库配置,这个开关能复用pom属性,不用维护两份配置。

4.2 targetPath:改资源在构建产物中的位置

targetPath的作用是重定义资源在输出目录中的位置。比如想让配置文件打包后放到jar包根目录下的config文件夹,可以这么写:

<resource> <directory>src/main/resources</directory> <targetPath>config</targetPath> </resource>

这样target/classes下会生成config目录,jar包里也就有了config目录。

但这个标签有坑,我见过的翻车场景不少:

  • 改动了classpath根路径,如果代码里有ClassPathResource("application.yml")这种写法,就找不到了
  • 对war包而言,targetPath的行为受war插件影响,有时候会把资源放到了预期以外的位置
  • 经常有人用它来解决“jar包启动后读不到外部配置”的问题,但这不是最优解,外部化配置更适合用spring.config.location这类参数

我的建议是:基础项目能不用targetPath就不用,它是种“强行改变产物结构”的手段,不要滥用。

4.3 没有特殊需求时,这两个标签可以完全省略

你要是问:我的项目只用了标准目录,不做环境替换,不搞自定义资源目录,也不排除文件,那需要写resources和testResources吗?

答案是:完全不需要。

Maven默认行为已经覆盖了最常见的场景。很多人学到一个配置就往上加,结果反而把默认的src/main/resources覆盖掉,导致配置文件丢失。资源文件配置的原则是“按需显式声明”,默认行为够用就别动,需要再加。如果你看到网上分享的项目pom里有大段resources配置,要先弄清楚他的项目为什么要这么配,而不是直接抄。

5. 我踩过和排查过的三个资源问题

5.1 过滤器把二进制文件改坏了

几年前维护一个老项目,项目里有几个静态资源文件,logo图片、字体文件,也放在src/main/resources下。当时为了处理config里的占位符,我给resources统一加了filtering=true。构建的时候日志非常正常,没有任何报错,但程序起来后图片加载不出来,打开target/classes下面的图片文件一看,比原始文件大了一圈,内容被替换过。

原因是二进制文件里存在某种字节序列,恰好被Maven的资源过滤识别成了占位符,在替换过程中被改写了。Maven并不会对图片、字体和文本文件区别对待,它只看文件内容的字节模式,一旦起了过滤,二进制文件就可能被污染。

从此我的配置习惯就是:文本类资源和二进制类资源分开目录,二进制目录保持filtering=false,或者用includes把参与过滤的文件类型限定为properties、xml、yaml这类纯文本格式。这个教训也直接写进了我给团队定的资源目录规范里。

5.2 显式声明resources之后,默认目录丢了

另一个印象很深的排查案例:同事在pom里加了一个额外资源目录,但只写了那一个resource:

<resources> <resource> <directory>src/main/extra</directory> </resource> </resources>

结果打出来的jar包里面没了application.yml,测试环境启动直接报配置缺失。他第一反应是怀疑打包插件有问题,折腾了很久才想到是resources配置把默认行为顶掉了。

这个问题讲清楚了其实很简单:当你显式声明resources时,Maven不再自动处理src/main/resources。你以为是在原有基础上增加,其实是把默认配置整体覆盖。所以写了自定义resources,一定要把默认目录也补回来,除非你真的打算放弃它。

为了避免其他人踩同一个坑,我会在代码评审里特别留意resources标签,见到只写额外目录不写默认目录的,直接提醒。

5.3 测试资源同名文件覆盖了主资源

第三个案例更隐蔽。项目里需求方给了一套Excel模板,放在src/main/resources/templates下,代码在运行时读取它并生成导出文件。有一次测试人员反馈导出的文件格式不对,我本地能复现但运行逻辑看起来没问题,最后打开target/test-classes/templates目录,发现里面躺着一个旧版本的模板文件。

原因就是src/test/resources/templates下也有一份同名文件,可能是某个开发阶段为了测试方便放进去的,后来一直没删。由于测试类加载classpath时target/test-classes排在target/classes前面,测试执行时读到的模板是旧的那份,于是行为异常。

解决方式很简单:测试资源目录里不要放和主资源同名的文件。如果确实需要测试专用的资源副本,也要让代码在读取时区分逻辑,或者给文件起不同的名字,避免shadowing。

如果非要在pom层面处理,可以在testResources里用excludes把测试资源中和主资源冲突的路径排除掉:

<testResources> <testResource> <directory>src/test/resources</directory> <filtering>true</filtering> <excludes> <exclude>templates/**</exclude> </excludes> </testResource> </testResources>

但更干净的做法还是文件命名上就避开,不要制造歧义。

这三个案例背后其实是同一个原则:Maven的资源处理是很底层的复制替换机制,它不识别文件含义,也不懂“要不要覆盖”,一切按你的配置机械执行。所以配置resources时要明确想要什么、不想要什么,别留任何模糊地带。花了半天在pom里排错的人,大多不是不知道标签怎么拼,而是没想清楚默认行为是什么。写配置前先把目录结构理清楚,运行时的一半问题都能提前消掉。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询