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

问题描述 投票:8回答:5

以下代码将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;
}
java classloader java-9 urlclassloader
5个回答
6
投票

您已经碰到the system class loader is no longer a URLClassLoader的事实。如URLClassLoader的返回类型所指示,这是一个实现细节,尽管依赖于不可忽略的代码量。

通过注释判断,您正在寻找一种在运行时动态加载类的方法。作为ClassLoader::getSystemClassLoader,在Java 9中无法通过附加到类路径来完成。

您应该考虑为此创建一个新的类加载器。这具有附加的优势,因为新类没有加载到应用程序类加载器中,因此您将能够摆脱它们。如果您使用Java 9进行编译,则应阅读Alan Bateman points out-它们为您提供了一个全新的抽象概念,用于加载全新的模块图。


3
投票

前一段时间,我偶然发现了这个问题。我使用了很多类似问题的方法

layers

在运行时动态将路径添加到类路径。该问题中的代码在多个方面可能是不好的样式:1)假设private static int AddtoBuildPath(File f) 返回ClassLoader.getSystemClassLoader()是未记录的实现细节,并且2)使用反射使URLClassLoader公开可能是另一种形式。

更干净的动态添加类路径的方法

如果需要通过“ addURL”使用附加的类路径URL进行类加载,则以下一种干净,优雅且兼容(Java 8到10)的解决方案:

1)通过使用公共Class.forName方法扩展URL类加载器来编写自己的类加载器>

addURL

2)声明一个类加载器的(单个/应用程序范围内的)对象

public class MyClassloader extends URLClassLoader {

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

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

并通过]实例化>

private final MyClassloader classLoader;

注意:系统类加载器是父级。通过classLoader = new MyClassloader(new URL[0], this.getClass().getClassLoader()); 加载的类知道可以通过classLoader加载的类,但反之则不行。

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

this.getClass().getClassLoader()

4)通过您的单例类加载器实例化对象或您的应用程序>

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

注意:由于类加载器在加载类之前先尝试委托给父类加载器(父类加载给其父类),因此必须确保要加载的类对父类加载器不可见,以确保它通过给定的类加载器加载。为了更清楚地说明这一点:如果系统类路径上有cls = Class.forName(name, true, classLoader); ,然后在自定义ClassPathB中添加ClassPathB和一些ClassPathA,则classLoader下的类将通过系统类加载器加载,而他们不知道ClassPathB。但是,如果从系统类路径中删除ClassPathA,则将通过您的自定义ClassPathB加载此类,然后ClassPathB下的类就会知道ClassPathA下的类。

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

classLoader

如果线程使用setContextClassLoader(classLoader)

getContextClassLoader指向Shadov处的线程。有正确答案:

oracle community

提到的注意事项也很重要:

注意事项:

java.util.ServiceLoader使用线程的ClassLoader上下文Thread.currentThread()。setContextClassLoader(specialloader);

java.sql.DriverManager确实支持调用类的ClassLoader,而不是线程的ClassLoader。直接使用Class.forName(“ drivername”,true,new URLClassLoader(urlarrayofextrajarsordirs).newInstance();

创建驱动程序

javax.activation使用线程的ClassLoader上下文(对于javax.mail重要)。

[如果您只是想读取当前的类路径,例如,因为您想启动另一个具有与当前类路径相同的类路径的JVM,则可以执行以下操作:

Class.forName("nameofclass", true, new URLClassLoader(urlarrayofextrajarsordirs));

默认情况下,无法通过反射访问$ AppClassLoader类中的最终字段,需要将额外的标志传递给JVM:

object ClassloaderHelper {
  def getURLs(classloader: ClassLoader) = {
    // jdk9+ need to use reflection
    val clazz = classloader.getClass

    val field = clazz.getDeclaredField("ucp")
    field.setAccessible(true)
    val value = field.get(classloader)

    value.asInstanceOf[URLClassPath].getURLs
  }
}

val classpath =
  (
    // jdk8
    // ClassLoader.getSystemClassLoader.asInstanceOf[URLClassLoader].getURLs ++
    // getClass.getClassLoader.asInstanceOf[URLClassLoader].getURLs

    // jdk9+
    ClassloaderHelper.getURLs(ClassLoader.getSystemClassLoader) ++
    ClassloaderHelper.getURLs(getClass.getClassLoader)
  )

我找到了这个,并为我工作。

String pathSeparator = Syste .getProperty(“ path.separator”);String [] classPathEntries = System.getProperty(“ java.class.path”).split(pathSeparator);

来自网站--add-opens java.base/jdk.internal.loader=ALL-UNNAMED


2
投票

getContextClassLoader指向Shadov处的线程。有正确答案:

oracle community

提到的注意事项也很重要:


1
投票

[如果您只是想读取当前的类路径,例如,因为您想启动另一个具有与当前类路径相同的类路径的JVM,则可以执行以下操作:

Class.forName("nameofclass", true, new URLClassLoader(urlarrayofextrajarsordirs));

默认情况下,无法通过反射访问$ AppClassLoader类中的最终字段,需要将额外的标志传递给JVM:


0
投票

我找到了这个,并为我工作。

String pathSeparator = Syste .getProperty(“ path.separator”);String [] classPathEntries = System.getProperty(“ java.class.path”).split(pathSeparator);

来自网站--add-opens java.base/jdk.internal.loader=ALL-UNNAMED

© www.soinside.com 2019 - 2024. All rights reserved.