You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 05:43:14