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

如何配置Maven构建保留原始依赖Jar的可运行UberJar?

Maven构建保留原始依赖Jar的可执行UberJar方案

问题背景

我开发的应用用到BouncyCastle并注册其SecurityProvider,使用maven-shade-plugin打UberJar时,所有依赖会被解压重打包,导致在Oracle JRE中运行失败——Oracle JRE仅接受由自身或Sun PKI证书签名的Jar文件中的SecurityProvider,解压重打包会破坏原Jar的签名验证逻辑。

现有方案的局限

  • 限定用户使用特定JRE:严重影响用户体验
  • 将依赖放在Jar旁的lib目录并配置classpathPrefix:交付物复杂度提升,需要用户额外处理目录结构

期望的解决方案

生成单个可执行Jar交付用户,无需用户处理额外目录:

  • Jar内保留所有(或指定部分)原始未修改的依赖Jar
  • 这些依赖Jar的内容(类、签名等)能在运行时被类加载器正常访问
  • 兼容Oracle JRE,保证BouncyCastle这类带签名的SecurityProvider能通过验证

解决方案配置

方案1:使用maven-onejar-plugin(推荐,配置简单)

该插件会将所有依赖Jar打包到生成的可执行Jar内部的lib/目录,通过内置类加载器加载原始Jar,完整保留依赖Jar的签名信息。

在pom.xml中添加插件配置:

<build>
    <plugins>
        <plugin>
            <groupId>org.dstovall</groupId>
            <artifactId>onejar-maven-plugin</artifactId>
            <version>1.4.4</version>
            <executions>
                <execution>
                    <goals>
                        <goal>one-jar</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <!-- 替换为你的应用主类全路径 -->
                <mainClass>com.yourpackage.YourMainClass</mainClass>
                <!-- 可选:排除不需要打包的依赖,格式为groupId:artifactId -->
                <excludes>
                    <exclude>example-group:example-artifact</exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

执行构建命令:

mvn clean package

生成的可执行Jar位于target/目录下,命名格式为{artifactId}-{version}-onejar.jar,直接运行:

java -jar target/your-app-1.0.0-onejar.jar

方案2:使用maven-assembly-plugin(自定义程度更高)

通过自定义assembly描述符,将依赖Jar打包到可执行Jar内部的lib/目录,同时编写自定义启动类加载内部Jar。

  1. 在项目src/main/resources/下创建assembly描述符文件assembly.xml:
<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>uberjar-with-deps</id>
    <formats>
        <format>jar</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <outputDirectory>/lib</outputDirectory>
            <useProjectArtifact>false</useProjectArtifact>
            <unpack>false</unpack> <!-- 关键:不解压依赖Jar,保留原始文件 -->
            <scope>runtime</scope>
        </dependencySet>
    </dependencySets>
    <fileSets>
        <fileSet>
            <directory>${project.build.outputDirectory}</directory>
            <outputDirectory>/</outputDirectory>
        </fileSet>
    </fileSets>
</assembly>
  1. 在pom.xml中配置maven-assembly-plugin,指定自定义启动类:
<build>
    <plugins>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.6.0</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                    <configuration>
                        <descriptors>
                            <descriptor>src/main/resources/assembly.xml</descriptor>
                        </descriptors>
                        <archive>
                            <manifest>
                                <!-- 替换为自定义启动类的全路径 -->
                                <mainClass>com.yourpackage.CustomLauncher</mainClass>
                            </manifest>
                        </archive>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
  1. 编写自定义启动类CustomLauncher.java,负责加载内部lib/目录下的所有Jar:
package com.yourpackage;

import java.io.JarFile;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;

public class CustomLauncher {
    public static void main(String[] args) throws Exception {
        // 获取当前Jar文件路径
        String jarPath = CustomLauncher.class.getProtectionDomain().getCodeSource().getLocation().getPath();
        JarFile jarFile = new JarFile(jarPath);
        List<URL> urls = new ArrayList<>();

        // 遍历Jar内的lib目录,收集所有依赖Jar的URL
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            if (entry.getName().startsWith("lib/") && entry.getName().endsWith(".jar") && !entry.isDirectory()) {
                URL url = new URL("jar:file:" + jarPath + "!/" + entry.getName());
                urls.add(url);
            }
        }
        jarFile.close();

        // 添加当前Jar到类加载器
        urls.add(new URL("file:" + jarPath));

        // 创建自定义类加载器并启动主类
        URLClassLoader classLoader = new URLClassLoader(urls.toArray(new URL[0]), ClassLoader.getSystemClassLoader().getParent());
        Class<?> mainClass = classLoader.loadClass("com.yourpackage.YourMainClass");
        mainClass.getMethod("main", String[].class).invoke(null, (Object) args);
    }
}

执行构建命令:

mvn clean package

生成的可执行Jar位于target/目录下,命名格式为{artifactId}-{version}-uberjar-with-deps.jar,直接运行:

java -jar target/your-app-1.0.0-uberjar-with-deps.jar

关键说明

  • 两种方案均不会解压依赖Jar,完整保留原始签名,因此能通过Oracle JRE的SecurityProvider签名验证
  • maven-onejar-plugin无需编写自定义启动类,配置更简单,适合大多数场景
  • maven-assembly-plugin自定义程度高,可灵活控制打包内容,但需要额外编写启动类

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 12:55:21