You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Java 9应用中如何通过类加载器编程加载数据库Jar至类路径?

Java 9中动态加载Jar到类路径的解决方案

我之前升级Java版本的时候也踩过这个坑!Java 9引入模块化系统后,系统类加载器(Application ClassLoader)的实现不再继承URLClassLoader了,所以你之前那种强制类型转换的写法肯定会抛出ClassCastException,太坑了😅。

为什么旧代码失效?

在Java 8及之前,系统类加载器是URLClassLoader的子类,直接强转后调用addURL方法没问题。但Java 9开始,系统类加载器换成了jdk.internal.loader.ClassLoaders$AppClassLoader,它继承自BuiltinClassLoader,和URLClassLoader没有继承关系,强转自然失败。

适配Java 9的两种方案

方案1:反射调用内部的addURL方法(兼容旧代码)

虽然系统类加载器不再是URLClassLoader,但它的父类BuiltinClassLoader依然提供了addURL方法(只是访问权限是包私有),我们可以通过反射来调用它,实现和旧代码一样的效果:

import java.lang.reflect.Method;
import java.net.URL;

public class JarLoader {
    public static void addJarToClasspath(URL jarUrl) throws Exception {
        // 获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        // 递归查找addURL方法(遍历类继承链)
        Method addUrlMethod = findAddUrlMethod(systemClassLoader.getClass());
        addUrlMethod.setAccessible(true);
        // 调用方法添加Jar路径
        addUrlMethod.invoke(systemClassLoader, jarUrl);
    }

    private static Method findAddUrlMethod(Class<?> clazz) throws NoSuchMethodException {
        try {
            // 先尝试当前类的addURL方法
            return clazz.getDeclaredMethod("addURL", URL.class);
        } catch (NoSuchMethodException e) {
            // 当前类没有就递归找父类
            Class<?> superClass = clazz.getSuperclass();
            if (superClass == null) {
                throw e;
            }
            return findAddUrlMethod(superClass);
        }
    }
}

这种方式不需要修改太多旧代码,能快速适配Java 9+,但要注意:这依赖JDK的内部实现,未来Java版本如果修改了addURL的定义,可能会失效,属于临时兼容方案。如果是Java 16+,还需要在启动参数里添加--add-opens java.base/jdk.internal.loader=ALL-UNNAMED,绕过模块封装规则的限制。

方案2:使用模块化API加载(推荐新应用)

如果你的应用已经迁移到Java模块化系统,推荐用ModuleLayer来动态加载模块化Jar,这种方式更符合Java 9+的设计规范:

import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Set;
import java.util.stream.Collectors;

public class ModuleJarLoader {
    public static void loadModularJar(URL jarUrl) throws Exception {
        // 将Jar URL转换为Path对象
        Path jarPath = Paths.get(new URI(jarUrl.toString()));
        // 基于启动模块层创建新的模块查找器
        ModuleLayer parentLayer = ModuleLayer.boot();
        ModuleFinder moduleFinder = ModuleFinder.of(jarPath);
        // 提取Jar中的模块名称
        Set<String> moduleNames = moduleFinder.findAll().stream()
                .map(ref -> ref.descriptor().name())
                .collect(Collectors.toSet());
        // 解析并加载模块到新的模块层
        Configuration config = parentLayer.configuration()
                .resolve(moduleFinder, ModuleFinder.of(), moduleNames);
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        ModuleLayer newLayer = parentLayer.defineModulesWithOneLoader(config, systemClassLoader);
        // 验证模块加载结果
        newLayer.modules().forEach(module -> 
                System.out.println("成功加载模块:" + module.getName()));
    }
}

这种方式更规范,但只适合模块化的Jar;如果是非模块化的普通Jar,还是方案1更直接。

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

火山引擎 最新活动