Java 动态下载 Maven Jar 包(附 DBeaver 底层实现原理)

Rainy BlogRainy Blog
约 37 分钟 424 阅读 0 赞
Java 动态下载 Maven Jar 包(附 DBeaver 底层实现原理)

平时开发、写小工具、本地调试时,经常会遇到一个实用需求:根据 Maven 坐标(groupId、artifactId、version),自动把 Jar 包下载到本地

用过 DBeaver 的朋友应该都有印象:新建数据库连接时,工具会自动下载对应的 MySQL、PostgreSQL、Oracle 驱动,不用我们手动找包、导入配置,全程无感、非常方便。

很多人好奇它的底层实现:到底是调用了本地 Maven 命令?还是简单的 HTTP 文件下载?

这篇文章就用通俗直白的方式,分享 3 种 Java 下载 Maven Jar 的实战方案,对比各自的优缺点和适用场景,同时拆解 DBeaver 原生实现原理。所有代码都是最新稳定版本,无老旧依赖、无版本冲突,可直接复制复用。

一、三种实现方案整体对比

目前 Java 程序动态拉取 Maven Jar 包,主流就三种实现方式,适配不同场景,大家可以按需选用:

  1. 本地 Maven 命令调用:代码极简,零额外依赖,但必须本地安装配置 Maven
  2. 轻量 HTTP 直连下载:无需 Maven 环境、依赖轻便,只适合下载独立单 Jar,不支持依赖传递
  3. 内嵌 Eclipse Aether 内核:DBeaver、IDEA 同款方案,无需本地 Maven,完整支持 Maven 所有核心能力,是专业工具的首选

二、方案一:调用本地 Maven 命令(最简临时方案)

1. 实现思路

直接通过 Java 执行系统命令,调用本地 Maven 的 dependency:get 指令,根据坐标拉取对应的 Jar 包,属于拿来即用的临时方案。

2. 完整可运行代码

import java.io.File;
import java.io.IOException;

/**
 * 本地Maven命令下载Jar
 * 适合:本地调试、本机已配置Maven环境
 */
public class MvnCmdDownloadUtil {

    /**
     * @param groupId 项目组织ID
     * @param artifactId 项目名称
     * @param version 版本号
     * @param saveDir Jar保存目录
     */
    public static void downloadJarByMvnCmd(String groupId, String artifactId, String version, String saveDir) throws IOException, InterruptedException {
        File dir = new File(saveDir);
        if (!dir.exists()) {
            dir.mkdirs();
        }

        // 下载命令:transitive=false 仅下载当前Jar,不拉取依赖包
        String[] cmd = new String[]{
                "mvn",
                "dependency:get",
                "-DgroupId=" + groupId,
                "-DartifactId=" + artifactId,
                "-Dversion=" + version,
                "-Ddest=" + saveDir,
                "-Dtransitive=false"
        };

        // 执行命令并打印日志
        Process process = new ProcessBuilder(cmd)
                .inheritIO()
                .directory(new File(saveDir))
                .start();
        int exitCode = process.waitFor();

        if (exitCode != 0) {
            throw new RuntimeException("Jar下载失败,命令退出码:" + exitCode);
        }
        System.out.println("Jar包下载完成,保存目录:" + saveDir);
    }

    public static void main(String[] args) throws Exception {
        // 测试下载 fastjson2
        downloadJarByMvnCmd("com.alibaba.fastjson2", "fastjson2", "2.0.32", "D:/java-libs");
    }
}

3. 方案优劣

✅ 优点:无需引入任何第三方依赖、代码极少、上手零成本

❌ 缺点:强依赖本地 Maven 环境,未配置 Maven 的设备无法运行,不适合线上项目和通用工具开发

三、方案二:轻量 HTTP 下载(无Maven依赖、轻量化首选)

1. 实现思路

Maven 仓库的文件有固定的 URL 规则,我们可以直接拼接仓库地址和 Maven 坐标路径,通过 HTTP 请求下载 Jar 包。全程不依赖 Maven 内核,依赖包轻量化、无版本冲突。

2. 项目依赖(新版稳定无冲突)

<!-- 新版HTTP客户端,替代老旧HttpClient4 -->
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.2.1</version>
</dependency>
<!-- 便捷文件工具类 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.16.1</version>
</dependency>

3. 完整可运行代码

import org.apache.commons.io.FileUtils;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;

import java.io.File;
import java.net.URI;

/**
 * 轻量化Maven Jar下载工具
 * 适合:仅下载单个独立Jar、追求低依赖、避免版本冲突的场景
 */
public class LightMavenDownloadUtil {
    // 阿里云Maven镜像(国内下载加速,替代官方中央仓库)
    private static final String MAVEN_ALI_REPO = "https://maven.aliyun.com/repository/public";

    public static File downloadJar(String groupId, String artifactId, String version, String targetDir) throws Exception {
        // 拼接标准Maven仓库路径
        String groupPath = groupId.replace(".", "/");
        String jarUrl = String.format("%s/%s/%s/%s/%s-%s.jar",
                MAVEN_ALI_REPO, groupPath, artifactId, version, artifactId, version);

        // 创建本地保存目录
        File saveDir = new File(targetDir);
        if (!saveDir.exists()) {
            saveDir.mkdirs();
        }
        File jarFile = new File(saveDir, artifactId + "-" + version + ".jar");

        // 执行HTTP下载
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet httpGet = new HttpGet(URI.create(jarUrl));
            httpClient.execute(httpGet, response -> {
                FileUtils.copyInputStreamToFile(response.getEntity().getContent(), jarFile);
                return null;
            });
        }
        return jarFile;
    }

    public static void main(String[] args) throws Exception {
        File jarFile = downloadJar("com.alibaba.fastjson2", "fastjson2", "2.0.32", "D:/java-libs");
        System.out.println("轻量下载完成,文件路径:" + jarFile.getAbsolutePath());
    }
}

4. 方案优劣

✅ 优点:依赖少、版本新、无冲突、下载速度快,完全不依赖 Maven 环境

❌ 缺点:不支持依赖传递解析,只会下载指定的单个 Jar;无法识别 RELEASE 动态版本、带 classifier 的特殊 Jar 包

四、方案三:内嵌Aether内核(DBeaver 同款专业实现)

这是 IDEA、DBeaver 等主流 Java 工具的底层实现方案,也是生产级开发的最优选择。通过内嵌 Maven 官方解析内核,不用本地安装 Maven,就能完整复刻 Maven 的所有依赖处理能力。

1. 核心优势

支持依赖树解析、自动下载传递依赖、自定义私服/镜像、动态版本、Jar 分类器、本地缓存等完整 Maven 特性,完美适配 JDBC 驱动、插件包等复杂 Jar 下载场景。

2. 最新稳定依赖(适配 JDK8~JDK21)

摒弃网上老旧的 1.4.2、3.8.6 旧版本,修复 HTTPS 证书、镜像超时、连接失败等常见问题:

<!-- 新版Aether Maven解析内核 -->
<dependency>
    <groupId>org.eclipse.aether</groupId>
    <artifactId>aether-api</artifactId>
    <version>1.4.4</version>
</dependency>
<dependency>
    <groupId>org.eclipse.aether</groupId>
    <artifactId>aether-impl</artifactId>
    <version>1.4.4</version>
</dependency>
<dependency>
    <groupId>org.eclipse.aether</groupId>
    <artifactId>aether-connector-basic</artifactId>
    <version>1.4.4</version>
</dependency>
<dependency>
    <groupId>org.eclipse.aether</groupId>
    <artifactId>aether-transport-file</artifactId>
    <version>1.4.4</version>
</dependency>
<dependency>
    <groupId>org.eclipse.aether</groupId>
    <artifactId>aether-transport-http</artifactId>
    <version>1.4.4</version>
</dependency>

<!-- 适配新版Maven核心 -->
<dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-aether-provider</artifactId>
    <version>3.9.6</version>
</dependency>

3. 完整工具类代码

import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;

import java.io.File;
import java.util.Collections;

/**
 * DBeaver同款Jar下载工具
 * 内嵌Aether内核,完整复刻Maven核心能力
 */
public class AetherMavenDownloadUtil {

    // 初始化Maven解析内核
    private static RepositorySystem newRepositorySystem() {
        DefaultServiceLocator locator = new DefaultServiceLocator();
        locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
        locator.addService(TransporterFactory.class, FileTransporterFactory.class);
        locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
        return locator.getService(RepositorySystem.class);
    }

    // 创建仓库会话,配置本地缓存路径
    private static RepositorySystemSession newSession(RepositorySystem system, String localRepoPath) {
        DefaultRepositorySystemSession session = new DefaultRepositorySystemSession();
        LocalRepository localRepo = new LocalRepository(localRepoPath);
        session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo));
        return session;
    }

    // 核心下载方法:自动解析依赖、下载所有关联Jar
    public static File downloadJar(String groupId, String artifactId, String version, String localRepoPath) throws Exception {
        RepositorySystem system = newRepositorySystem();
        RepositorySystemSession session = newSession(system, localRepoPath);

        // 配置阿里云镜像加速
        RemoteRepository aliRepo = new RemoteRepository.Builder(
                "aliyun",
                "default",
                "https://maven.aliyun.com/repository/public"
        ).build();

        // 构建Maven坐标并发起解析请求
        Artifact artifact = new DefaultArtifact(groupId + ":" + artifactId + ":" + version);
        CollectRequest collectRequest = new CollectRequest();
        collectRequest.setRoot(new org.eclipse.aether.graph.Dependency(artifact, "compile"));
        collectRequest.setRepositories(Collections.singletonList(aliRepo));

        // 解析依赖并下载所有相关Jar包
        DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, null);
        org.eclipse.aether.resolution.DependencyResult result = system.resolveDependencies(session, dependencyRequest);

        // 匹配目标Jar文件并返回
        for (ArtifactResult artifactResult : result.getArtifactResults()) {
            Artifact art = artifactResult.getArtifact();
            if (art.getGroupId().equals(groupId) && art.getArtifactId().equals(artifactId)) {
                return art.getFile();
            }
        }
        throw new RuntimeException("未匹配到对应Jar文件,请检查Maven坐标是否正确");
    }

    public static void main(String[] args) throws Exception {
        // 自定义本地仓库缓存目录
        String localRepo = "D:/maven-local-repo";
        File jarFile = downloadJar("com.alibaba.fastjson2", "fastjson2", "2.0.32", localRepo);
        System.out.println("专业内核下载完成,Jar路径:" + jarFile.getAbsolutePath());
    }
}

五、硬核拆解:DBeaver 到底用的哪种方案?

直接说结论:DBeaver 全程使用内嵌 Aether 内核方案,和上面第三种代码逻辑完全一致,这也是专业 Java 工具的标准答案。

1. 为什么不用另外两种方案?

  • 舍弃 Maven 命令行:DBeaver 是绿色免安装工具,不可能要求用户本地必须安装、配置 Maven,兼容性太差
  • 舍弃简易 HTTP 下载:绝大多数 JDBC 驱动都有多层传递依赖,只下载单个 Jar 会导致驱动运行报错;同时无法适配动态版本、特殊分类器的驱动包

2. DBeaver 真实下载流程

  1. 读取内置驱动配置,获取对应数据库驱动的标准 Maven 坐标
  2. 调用内置 Aether 内核,加载用户配置的 Maven 镜像/私服地址
  3. 优先读取本地缓存仓库(默认路径:%APPDATA%\DBeaverData\drivers\maven\),有缓存直接复用,无需重复下载
  4. 无缓存则远程拉取 Pom 文件,递归解析、下载所有依赖 Jar 包
  5. 通过独立类加载器动态挂载 Jar,无需重启工具即可正常使用数据库驱动

六、方案选型指南(直接照着选即可)

实现方案适用场景推荐指数
本地Maven命令下载本地临时调试、设备已配置Maven环境⭐⭐
轻量HTTP下载仅下载独立单Jar、追求轻量化、规避依赖冲突⭐⭐⭐⭐
内嵌Aether内核工具开发、动态加载驱动、需要解析完整依赖树、线上正式项目⭐⭐⭐⭐⭐

七、实战避坑小结

整理几个实际开发中高频遇到的问题,帮大家少走弯路:

  1. 尽量避开老旧的 Aether1.4.2、Maven3.8.6 版本,大概率出现 HTTPS 证书异常、镜像超时等问题,优先使用文中新版依赖
  2. 如果需要下载 JDBC 驱动、插件等带依赖的 Jar 包,必须用 Aether 方案,轻量 HTTP 方案无法满足依赖解析需求
  3. 国内开发统一使用阿里云 Maven 镜像,能大幅提升下载速度,避免中央仓库连接超时、下载失败
  4. SpringBoot 项目优先选用轻量 HTTP 方案,可有效规避 Maven 内核带来的依赖冲突
Rainy Blog

Rainy Blog

技术分享 · 产品展示 · 个人成长

评论 (0)

游客评论需审核:您提交的内容将在管理员审核通过后才公开展示,请耐心等待,请勿重复提交

暂无评论,来说两句吧~

Rainy BlogRainy Blog

技术分享 · 产品展示 · 个人成长。记录代码与思考,分享有用、有趣的内容。

关注我们

© 2026 Rainy Studio. 保留所有权利。

京ICP备17068238号