Maven打包含依赖可执行Jar时,META-INF文件致外部conf资源加载失败
我尝试用Maven构建包含所有依赖的可执行Jar包,同时指定ClassPath加载外部conf目录的资源。当前的maven-assembly-plugin配置如下:
... <plugin> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals><goal>single</goal></goals> </execution> </executions> <configuration> <appendAssemblyId>false</appendAssemblyId> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifestEntries> <Version>${project.version}</Version> <Class-Path>conf/</Class-Path> <Main-Class>${exec.mainClass}</Main-Class> </manifestEntries> </archive> </configuration> </plugin> ...
生成的Jar包中MANIFEST.MF内容如下:
Manifest-Version: 1.0 Created-By: Maven Archiver 3.6.1 Build-Jdk-Spec: 17 Class-Path: conf/ Main-Class: gina.report.Main Version: 2.0.0
现在遇到的问题是:Jar包的/META-INF目录下生成了大量额外文件和目录,删除这些文件只保留MANIFEST.MF时,程序能正常加载conf目录的资源;但保留这些文件时,ClassLoader就无法加载该目录下的资源。请问这是什么原因?
核心原因
使用jar-with-dependencies描述符打包时,maven-assembly-plugin会把所有依赖Jar的内容解压合并到最终Jar中,包括依赖Jar里/META-INF目录下的文件(比如INDEX.LIST、services/目录、依赖自身的MANIFEST.MF等)。
其中**INDEX.LIST是关键问题**:这个文件是JAR资源索引,由Jar工具生成,记录了Jar内部所有资源路径。JVM加载Jar时,如果存在这个文件,会优先用它查找资源,完全跳过Class-Path指定的外部目录,导致conf目录的资源被ClassLoader忽略。
解决方法
方法1:自定义Assembly描述符,排除不必要的META-INF文件
放弃直接使用jar-with-dependencies,自己编写assembly描述符文件(如src/main/assembly/assembly.xml),明确排除INDEX.LIST及冗余签名文件:
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd"> <id>jar-with-dependencies</id> <formats> <format>jar</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <dependencySets> <dependencySet> <outputDirectory>/</outputDirectory> <useProjectArtifact>true</useProjectArtifact> <unpack>true</unpack> <scope>runtime</scope> <excludes> <exclude>META-INF/INDEX.LIST</exclude> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </dependencySet> </dependencySets> <archive> <manifestEntries> <Version>${project.version}</Version> <Class-Path>conf/</Class-Path> <Main-Class>${exec.mainClass}</Main-Class> </manifestEntries> </archive> </assembly>
然后修改pom.xml中的插件配置,指定自定义描述符:
<plugin> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals><goal>single</goal></goals> <configuration> <appendAssemblyId>false</appendAssemblyId> <descriptors> <descriptor>src/main/assembly/assembly.xml</descriptor> </descriptors> </configuration> </execution> </executions> </plugin>
方法2:替换为maven-shade-plugin
maven-shade-plugin打包可执行Jar时,默认不会生成INDEX.LIST,还能智能合并META-INF下的服务配置文件,避免冲突。配置示例:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.4.1</version> <executions> <execution> <phase>package</phase> <goals><goal>shade</goal></goals> <configuration> <shadedArtifactAttached>false</shadedArtifactAttached> <shadedClassifierName>jar-with-dependencies</shadedClassifierName> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Version>${project.version}</Version> <Class-Path>conf/</Class-Path> <Main-Class>${exec.mainClass}</Main-Class> </manifestEntries> </transformer> </transformers> </configuration> </execution> </executions> </plugin>
内容的提问来源于stack exchange,提问作者Maurice Perry

