在Java项目里,我们经常需要把XML文件如MyBatis的Mapper、Spring的上下文或者一些规则配置打进最终的JAR包中。Maven作为主流构建工具,其资源复制机制决定了哪些文件会被放到classpath里。如果配置不当,编译没问题,但运行起来却报文件找不到,这多半是pom.xml里build resources的设置没有覆盖到你的XML位置。

一、Maven默认资源打包行为
Maven的standard directory layout规定,src/main/resources目录下的文件会在process-resources阶段复制到target/classes,并最终进入JAR。但如果你把XML放在src/main/java的某个包下,或者文件名带特殊后缀,默认规则就不会复制它们。
另外,Maven对资源文件有“过滤”概念。开启filtering后,文件里的${}占位符会被替换。XML里常出现类似Spring的占位符,若误开过滤又没配属性,文件内容可能被破坏甚至清空。理解默认行为,是写对配置的前提。
1.1 默认不复制源码目录中的XML
比如你在src/main/java/com/demo/下写了UserMapper.xml,执行package后,JAR里根本没有这个文件。因为Maven只把java文件交给编译器,其他类型留在源码树,不进resources流程。
这种结构在MyBatis老项目中很常见。要解决这个问题,就得在pom.xml明确告诉Maven:这个目录里的XML也是资源,请帮我复制。
二、用build resources标签打包XML
最核心的方式是在pom.xml的<build>内写<resources>,每一个<resource>描述一个资源集合。你可以指定directory、includes、excludes和targetPath。
下面示例把源码目录里的XML和resources目录里的所有文件都打进JAR,并且关闭过滤以防内容被改:
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
2.1 directory与targetPath说明
directory是项目中的源目录,targetPath是打进JAR后的目标路径,默认就是根(对应classpath根)。如果你想把XML放到JAR里的config目录,可写<targetPath>config</targetPath>,但注意代码里加载路径也要随之改变。
includes使用Ant风格匹配,**/*.xml表示任意级子目录下的XML。如果只想要某个包,可以写<include>com/demo/mapper/*.xml</include>。excludes则用来排除,比如测试用XML不进生产包。
2.2 避免过滤导致XML损坏
如果某个resource开了<filtering>true</filtering>,而XML里有<property name="${name}"/>之类内容,Maven会尝试找属性替换。找不到就可能留空或报错。对XML资源,一般建议保持false。
当你确实需要对properties过滤、但不想动XML时,就把它们拆成两个resource块,一个开过滤一个关过滤,互不干扰。
三、使用maven-resources-plugin精确控制
除了在build里声明,也可以显式用插件绑定阶段。适合需要多套资源、或者想在不同profile里复制不同XML的场景。
示例插件配置如下,效果等同于上面的resources声明,但更直观,也方便加executions:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>copy-xml</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}</outputDirectory>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
3.1 插件方式优缺点
优点是可以利用profile激活不同execution,比如开发环境复制mock XML,生产环境复制真实XML。也可以把文件复制到任意outputDirectory,不限于classes。
缺点是配置比纯resources标签啰嗦,新手容易把phase写错导致没执行。一般小项目直接用build resources就够了,复杂多环境再上插件。
四、验证与常见问题
打包后,用jar tf target/your-app.jar列出内容,确认XML路径符合预期。若用Spring Boot,默认也会尊重你pom里的resources,但注意它的repackage会把原JAR嵌套,看内容要解开boot包。
常见坑:XML放在resources但被<excludes>误杀;或者IDE里能跑,因为IDE自己复制了,而Maven没配。还有就是把directory写成绝对路径,导致换机器就失败。坚持用相对路径和清晰includes,基本能稳住。
4.1 简单校验代码
可以在程序里写个简单检查,确认XML确实在classpath:
import java.io.InputStream;
public class CheckXml {
public static void main(String[] args) {
// 尝试从classpath加载MapperXML
InputStream in = CheckXml.class.getClassLoader()
.getResourceAsStream("com/demo/UserMapper.xml");
if (in == null) {
System.out.println("XML未打包进JAR");
} else {
System.out.println("XML已存在");
}
}
}
这段程序打进同一个JAR运行,如果输出未打包,就回头查pom的directory和include是否匹配。路径要对应targetPath,默认无前缀就是根开头。
五、总结建议
把XML打进JAR并不难,关键是让Maven知道去哪找、复制什么、别乱改。首选在build resources里加directory和includes,关闭filtering;特殊需求再用resources-plugin。打包后务必用jar命令或代码验证,别等上线才发现缺文件。
只要配置清晰,MyBatis映射、Spring自定义schema、规则引擎脚本等都能随包发布,部署时少踩很多坑。
Mavenpom_xmlbuild_resources修改时间:2026-08-03 22:12:21