Java 9,ClassLoader.getSystemClassLoader 的兼容性问题

2022-09-02 12:53:37

下面的代码将jar文件添加到构建路径中,它适用于Java 8。但是,它在 Java 9 中引发异常,该异常与强制转换为 URLClassLoader 有关。任何想法如何解决这个问题?最佳解决方案将对其进行编辑以同时与Java 8和9一起使用。

private static int AddtoBuildPath(File f) {
    try {
        URI u = f.toURI();
        URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        Class<URLClassLoader> urlClass = URLClassLoader.class;
        Method method = urlClass.getDeclaredMethod("addURL", URL.class);
        method.setAccessible(true);
        method.invoke(urlClassLoader, u.toURL());
    } catch (NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException | MalformedURLException | IllegalAccessException ex) {
        return 1;
    }

    return 0;
}

答案 1

您已经遇到了这样一个事实,即系统类装入器不再是 URLClassLoader。如 的返回类型所示,这是一个实现细节,尽管它依赖于不可忽略的代码量。ClassLoader::getSystemClassLoader

从注释来看,您正在寻找一种在运行时动态加载类的方法。正如 Alan Bateman 所指出的,这在 Java 9 中无法通过附加到类路径来完成。

相反,您应该考虑为此创建一个新的类装入器。这具有额外的优势,即您将能够摆脱新类,因为它们不会加载到应用程序类加载器中。如果你正在针对Java 9进行编译,你应该阅读 - 它们给你一个干净的抽象来加载一个全新的模块图。


答案 2

我前一阵子偶然发现了这个问题。与许多人一样,我使用了与问题类似的方法

private static int AddtoBuildPath(File f)

以在运行时动态添加类路径的路径。问题中的代码可能在多个方面都是不好的风格:1)假设返回a是一个未记录的实现细节,2)使用反射来公开可能是另一个。ClassLoader.getSystemClassLoader()URLClassLoaderaddURL

动态添加类路径的更简洁方法

如果您需要使用其他类路径 URL 通过 “” 加载类,则干净、优雅且兼容(Java 8 到 10)的解决方案如下:Class.forName

1)通过扩展URL类装入器编写自己的类装入器,具有公共方法addURL

public class MyClassloader extends URLClassLoader {

    public MyClassloader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    public void addURL(URL url) {
        super.addURL(url);
    }
}

2) 声明类装入器的(单例/应用范围)对象

private final MyClassloader classLoader;

并通过以下方式将其实例化

classLoader = new MyClassloader(new URL[0], this.getClass().getClassLoader());

注: 系统类装入器是父级。加载的类虽然知道那些可以加载的人,但反过来不是相反。classLoaderthis.getClass().getClassLoader()

3) 在需要时添加其他类路径(动态):

File file = new File(path);
if(file.exists()) {
    URL url = file.toURI().toURL();
    classLoader.addURL(url);
}

4) 通过单例类加载器通过实例化对象或应用

cls = Class.forName(name, true, classLoader);

注: 由于类装入器在装入类之前尝试委派给父类装入器(以及父类装入器的父类装入器),因此必须确保要装入的类对父类装入器不可见,以确保它是通过给定的类装入器装入的。为了更清楚地说明这一点:如果您在系统类路径上有,并且稍后将添加和一些添加到您的自定义中,那么下面的类将通过系统类加载器加载,并且它们不知道下面的类。但是,如果从系统类路径中删除,则将通过自定义加载此类类,然后 ClassPathA 下的类已知于 ClassPathB 下的类。ClassPathBClassPathBClassPathAclassLoaderClassPathBClassPathAClassPathBclassLoader

5) 您可以考虑通过以下方式将类加载器传递给线程

setContextClassLoader(classLoader)

如果该线程使用 .getContextClassLoader


推荐