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

部署可执行jar为何需两处提供依赖,能否直接读取jar内部依赖?

解决方案

Java默认的系统类加载器不支持直接读取Jar包内部嵌套的Jar文件,要实现单Jar部署,有以下两种成熟的编程层面实现方案:

方案1:自定义类加载器实现嵌套Jar加载

这是最底层的实现方式,核心逻辑如下:

  • 继承ClassLoader重写类加载逻辑,在findClass方法中优先读取当前可执行Jar内部的lib/路径下的内嵌Jar资源
  • 读取内嵌Jar的字节流后,调用defineClass完成类的定义加载
  • 同时需要重写资源加载相关方法(getResource、getResourceAsStream等),适配内嵌资源的读取路径

核心实现的关键代码示例:

public class NestedJarClassLoader extends ClassLoader {
    private final Map<String, byte[]> classCache = new HashMap<>();

    public NestedJarClassLoader() throws IOException {
        // 读取当前可执行Jar内的所有内嵌Jar
        try (JarFile mainJar = new JarFile(new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()))) {
            Enumeration<JarEntry> entries = mainJar.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                // 匹配lib目录下的Jar文件
                if (entry.getName().startsWith("lib/") && entry.getName().endsWith(".jar")) {
                    try (InputStream is = mainJar.getInputStream(entry);
                         JarFile nestedJar = new JarFile(new TempFile(entry.getName(), is).toFile())) {
                        // 遍历内嵌Jar的所有class文件,缓存字节码
                        Enumeration<JarEntry> nestedEntries = nestedJar.entries();
                        while (nestedEntries.hasMoreElements()) {
                            JarEntry nestedEntry = nestedEntries.nextElement();
                            if (nestedEntry.getName().endsWith(".class")) {
                                String className = nestedEntry.getName().replace("/", ".").replace(".class", "");
                                classCache.put(className, nestedJar.getInputStream(nestedEntry).readAllBytes());
                            }
                        }
                    }
                }
            }
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classBytes = classCache.get(name);
        if (classBytes == null) {
            return super.findClass(name);
        }
        return defineClass(name, classBytes, 0, classBytes.length);
    }
}

之后需要在主类的main方法最开头,将当前线程的上下文类加载器替换为自定义的类加载器,再执行后续业务逻辑。

方案2:复用现有打包工具的加载逻辑

如果不需要从零实现类加载逻辑,可以直接使用成熟工具的能力:

  • 如果用Maven构建,使用maven-shade-plugin插件,打包时会把所有内嵌Jar的类直接解压合并到主Jar的根目录,不需要额外自定义代码就能直接单Jar运行
  • 如果用Spring Boot生态,spring-boot-maven-plugin打包生成的可执行Jar内置了LaunchedURLClassLoader,天生支持加载Jar内部BOOT-INF/lib/路径下的嵌套依赖,不需要额外编码即可直接单Jar部署

注意:你当前MANIFEST.MF里配置的Class-Path: lib/library.jar默认是从执行Jar的工作目录下查找路径,不是从Jar内部查找,这也是你现在必须外置lib目录的核心原因。

内容的提问来源于stack exchange,提问作者lazyone

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 20:36:08