如何构建可自动下载依赖的非Fat Java Jar文件
实现可执行Jar自动下载依赖(不生成Fat Jar)
需求:构建Java项目并打包为可执行Jar,不将依赖打包进Jar中,运行时自动下载所需依赖保证程序正常执行。现有build.gradle如下:
/* * This file was generated by the Gradle 'init' task. * * This generated file contains a sample Java application project to get you started. * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle * User Manual available at https://docs.gradle.org/8.0.1/userguide/building_java_projects.html */ plugins { // Apply the application plugin to add support for building a CLI application in Java. id 'application' } repositories { // Use Maven Central for resolving dependencies. mavenCentral() } dependencies { // Use JUnit Jupiter for testing. testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' // This dependency is used by the application. implementation 'com.google.guava:guava:31.1-jre' implementation 'com.pi4j:pi4j-core:2.3.0' // implementation 'com.pi4j:pi4j-ktx:2.3.0' // Kotlin DSL implementation("com.pi4j:pi4j-plugin-raspberrypi:2.2.1") implementation("com.pi4j:pi4j-plugin-pigpio:2.2.1") } application { // Define the main class for the application. mainClass = 'new_folder.App' } jar { manifest { attributes 'Main-Class': application.mainClass } } tasks.named('test') { // Use JUnit Platform for unit tests. useJUnitPlatform() }
解决方案
1. 调整build.gradle配置
添加生成依赖清单的任务,修改Jar的主类为自定义启动器,并将真实业务主类存入Manifest:
plugins { id 'application' } repositories { mavenCentral() } dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' implementation 'com.google.guava:guava:31.1-jre' implementation 'com.pi4j:pi4j-core:2.3.0' implementation("com.pi4j:pi4j-plugin-raspberrypi:2.2.1") implementation("com.pi4j:pi4j-plugin-pigpio:2.2.1") } application { mainClass = 'new_folder.App' } // 生成依赖清单文件,包含所有运行时依赖的坐标 task generateDependenciesList { doLast { def dependenciesFile = file("$buildDir/resources/main/dependencies.txt") dependenciesFile.parentFile.mkdirs() configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts.each { artifact -> def module = artifact.moduleVersion.id dependenciesFile.append("${module.group}:${module.name}:${module.version}\n") } } } jar { dependsOn generateDependenciesList manifest { attributes 'Main-Class': 'new_folder.DependencyLoader', // 自定义启动器类 'App-Main-Class': application.mainClass // 存储真实业务主类 } } tasks.named('test') { useJUnitPlatform() }
2. 编写依赖加载启动器DependencyLoader.java
该类负责读取依赖清单、下载依赖到本地、动态加载类并启动主程序:
package new_folder; import java.io.*; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.jar.Manifest; public class DependencyLoader { private static final String DEPENDENCIES_FILE = "/dependencies.txt"; private static final String DEPENDENCY_DIR = "lib"; public static void main(String[] args) throws Exception { // 创建本地依赖存储目录 Path libDir = Paths.get(DEPENDENCY_DIR); Files.createDirectories(libDir); // 读取Jar内的依赖清单 List<String> dependencies = new ArrayList<>(); try (InputStream is = DependencyLoader.class.getResourceAsStream(DEPENDENCIES_FILE); BufferedReader br = new BufferedReader(new InputStreamReader(is))) { String line; while ((line = br.readLine()) != null) { dependencies.add(line.trim()); } } // 下载依赖到本地目录 List<URL> dependencyUrls = new ArrayList<>(); for (String dep : dependencies) { String[] parts = dep.split(":"); String group = parts[0]; String artifact = parts[1]; String version = parts[2]; // 构建Maven Central下载路径 String urlStr = String.format("https://repo1.maven.org/maven2/%s/%s/%s/%s-%s.jar", group.replace('.', '/'), artifact, version, artifact, version); Path destPath = libDir.resolve(String.format("%s-%s.jar", artifact, version)); // 依赖文件不存在则执行下载 if (!Files.exists(destPath)) { try (InputStream in = new URL(urlStr).openStream(); OutputStream out = Files.newOutputStream(destPath)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } } } dependencyUrls.add(destPath.toUri().toURL()); } // 从Manifest获取真实业务主类 Manifest manifest = new Manifest(DependencyLoader.class.getResourceAsStream("/META-INF/MANIFEST.MF")); String appMainClass = manifest.getMainAttributes().getValue("App-Main-Class"); // 加载依赖并启动主程序 URLClassLoader classLoader = new URLClassLoader(dependencyUrls.toArray(new URL[0]), DependencyLoader.class.getClassLoader()); Class<?> mainClass = classLoader.loadClass(appMainClass); mainClass.getMethod("main", String[].class).invoke(null, (Object) args); } }
3. 打包与运行
执行Gradle打包命令:
./gradlew jar
运行生成的可执行Jar:
java -jar build/libs/[你的项目名称].jar
首次运行会自动下载所有依赖到当前目录的lib文件夹,后续运行将直接复用本地已下载的依赖。
内容的提问来源于stack exchange,提问作者Chirag Suthar
相关产品推荐
相关产品推荐

