使用maven-assembly-plugin打包时lib依赖引发NoClassDefFoundError问题咨询
问题描述
我想生成一个包含依赖的可执行Jar包,要求依赖以Jar文件形式存放在lib/目录下,主应用自身解压到Jar顶层。但运行这个Jar时,实例化依赖对象会抛出NoClassDefFoundError;如果把依赖也解压到Jar顶层就能正常运行,请问缺少哪些配置?
原POM文件
<?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>jo.vpl</groupId> <artifactId>VPL-Core-Hubs</artifactId> <version>1</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>${project.groupId}</groupId> <artifactId>jo.vpl</artifactId> <version>2</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.7</version> <scope>compile</scope> </dependency> </dependencies> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> </properties> <build> <finalName>VPL-Core-Hubs</finalName> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <archive> <manifest> <mainClass>jo.vpl.hub.Main</mainClass> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> </manifest> </archive> <descriptors> <descriptor>${basedir}/assembly.xml</descriptor> </descriptors> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
原Assembly.xml文件
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd"> <id>jar-with-dependencies</id> <formats> <format>jar</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <dependencySets> <dependencySet> <unpack>true</unpack> <useProjectArtifact>true</useProjectArtifact> <outputDirectory>/</outputDirectory> <includes> <include>jo.vpl:VPL-Core-Hubs</include> </includes> </dependencySet> <dependencySet> <unpack>false</unpack> <useProjectArtifact>true</useProjectArtifact> <outputDirectory>lib/</outputDirectory> <includes> <include>com.google.code.gson:gson</include> </includes> </dependencySet> </dependencySets> </assembly>
问题原因与解决办法
核心问题是Java默认系统类加载器无法识别Jar包内部嵌套的Jar文件——哪怕你在Manifest里配置了classpath,也只能加载当前Jar外部的依赖,没法读取Jar内部lib/目录下的依赖Jar。下面给出两种可行方案:
方案1:改用maven-shade-plugin(推荐,最简单)
这个插件会自动处理嵌套Jar的类加载问题,替代你当前的maven-assembly-plugin,修改POM的build/plugins部分即可:
<build> <finalName>VPL-Core-Hubs</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.5.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <shadedArtifactAttached>true</shadedArtifactAttached> <shadedClassifierName>jar-with-dependencies</shadedClassifierName> <transformers> <!-- 指定主类 --> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>jo.vpl.hub.Main</mainClass> </transformer> <!-- 处理服务类和资源文件冲突 --> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> <transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer"> <resources> <resource>META-INF/LICENSE</resource> </resources> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build>
执行mvn package后,会生成一个带-jar-with-dependencies后缀的可执行Jar,直接运行即可。
方案2:修正assembly配置+自定义类加载器(保留lib目录结构)
如果一定要保留Jar内部的lib/目录,需要两步操作:
1. 修正Assembly.xml配置
你当前的第二个dependencySet错误地把主项目Jar也复制到了lib/目录,改成useProjectArtifact=false,同时明确依赖范围:
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd"> <id>jar-with-dependencies</id> <formats> <format>jar</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <dependencySets> <dependencySet> <unpack>true</unpack> <useProjectArtifact>true</useProjectArtifact> <outputDirectory>/</outputDirectory> <includes> <include>jo.vpl:VPL-Core-Hubs</include> </includes> </dependencySet> <dependencySet> <unpack>false</unpack> <useProjectArtifact>false</useProjectArtifact> <!-- 这里改为false,避免主Jar重复 --> <outputDirectory>lib/</outputDirectory> <includes> <include>com.google.code.gson:gson</include> </includes> <scope>compile</scope> <!-- 只包含编译依赖 --> </dependencySet> </dependencySets> </assembly>
2. 添加自定义类加载器
Java默认类加载器读不到Jar内部的lib/Jar,需要自己写一个类加载器来读取这些嵌套Jar:
import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class NestedJarClassLoader extends URLClassLoader { public NestedJarClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } public static NestedJarClassLoader create() throws IOException { // 获取当前Jar的路径 String jarPath = NestedJarClassLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath(); JarFile jarFile = new JarFile(jarPath); NestedJarClassLoader classLoader = new NestedJarClassLoader(new URL[0], ClassLoader.getSystemClassLoader()); // 遍历Jar内部的lib目录下的所有Jar文件 Enumeration<JarEntry> entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.getName().startsWith("lib/") && entry.getName().endsWith(".jar")) { URL url = new URL("jar:file:" + jarPath + "!/" + entry.getName()); classLoader.addURL(url); } } jarFile.close(); return classLoader; } public static void main(String[] args) throws Exception { NestedJarClassLoader classLoader = create(); // 加载主类并运行 Class<?> mainClass = classLoader.loadClass("jo.vpl.hub.Main"); mainClass.getMethod("main", String[].class).invoke(null, (Object) args); } }
然后修改POM的Manifest配置,把启动类改成这个自定义类加载器:
<archive> <manifest> <mainClass>jo.vpl.hub.NestedJarClassLoader</mainClass> <!-- 替换成自定义类加载器的全类名 --> </manifest> </archive>
这种方式需要额外维护自定义类加载器,适合有特殊目录结构需求的场景。
内容的提问来源于stack exchange,提问作者Josephus87
相关产品推荐
相关产品推荐

