如何配置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。
- 在项目
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>
- 在
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>
- 编写自定义启动类
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
相关产品推荐
相关产品推荐

