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

时间:2017-10-11 17:51:49

标签: java classloader java-9 urlclassloader

以下代码将jar文件添加到构建路径,它适用于Java 8.但是,它会抛出Java 9的异常,该异常与转换为URLClassLoader有关。有什么想法可以解决这个问题吗?最佳解决方案将对其进行编辑,以便与Java 8和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;
}

8 个答案:

答案 0 :(得分:3)

你遇到the system class loader is no longer a URLClassLoader这个事实。正如ClassLoader::getSystemClassLoader的返回类型所示,这是一个实现细节,尽管是一个不可忽略的代码量依赖的实现细节。

根据评论判断,您正在寻找一种在运行时动态加载类的方法。作为Alan Bateman points out,这不能通过附加到类路径在Java 9中完成。

您应该考虑为此创建一个新的类加载器。这有一个额外的好处,你将能够摆脱新类,因为它们没有加载到应用程序类加载器中。如果您正在编译Java 9,那么您应该阅读layers - 它们为您提供了一个干净的抽象来加载一个全新的模块图。

答案 1 :(得分:3)

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

private static int AddtoBuildPath(File f)

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

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

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

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());

注意:系统类加载器是父级。通过classLoader加载的类知道可以通过this.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);

注意:由于类加载器在加载类之前尝试了对父类加载器的委托(父类对其父对象的委托),因此您必须确保要加载的类对父类加载器不可见,以确保它是通过给定的类加载器加载的。为了使这一点更清楚:如果您的系统类路径上有ClassPathB,然后在自定义ClassPathB中添加ClassPathA和一些classLoader,则ClassPathB下的类将通过系统类加载器加载,并且ClassPathA下的类对其未知。但是,如果从系统类路径中删除ClassPathB,则将通过自定义classLoader加载此类,然后ClassPathB下的类将被ClassPathA下的类所知道。

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

setContextClassLoader(classLoader)

如果该线程使用getContextClassLoader

答案 2 :(得分:2)

Shadov指向oracle community的帖子。有正确的答案:

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

提到的警告也很重要:

  

注意事项:

     

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

     

java.sql.DriverManager确实尊重了调用类&#39; ClassLoader, - 不是Thread的ClassLoader。使用Class.forName直接创建驱动程序(&#34; drivername&#34;,true,new URLClassLoader(urlarrayofextrajarsordirs).newInstance();

     

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

答案 3 :(得分:1)

我得到了一个运行在Java 8中的spring boot应用程序。我有任务将其升级到Java 11版本。

面临的问题:

由以下原因导致:java.lang.ClassCastException:jdk.internal.loader.ClassLoaders $ AppClassLoader(在模块:java.base中)不能转换为java.net.URLClassLoader(在模块:java.base中)

使用方法:

创建课程:

import java.net.URL;

/**
 * This class has been created to make the code compatible after migration to Java 11
 * From the JDK 9 release notes: "The application class loader is no longer an instance of
 * java.net.URLClassLoader (an implementation detail that was never specified in previous releases).
 * Code that assumes that ClassLoader.getSytemClassLoader() returns a URLClassLoader object will
 * need to be updated. Note that Java SE and the JDK do not provide an API for applications or
 * libraries to dynamically augment the class path at run-time."
 */

public class ClassLoaderConfig {

    private final MockClassLoader classLoader;

    ClassLoaderConfig() {
        this.classLoader = new MockClassLoader(new URL[0], this.getClass().getClassLoader());
    }

    public MockClassLoader getClassLoader() {
        return this.classLoader;
    }
}

创建另一个课程:

import java.net.URL;
import java.net.URLClassLoader;

public class MockClassLoader extends URLClassLoader {

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

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

现在在您的主类的当前线程中设置它(在应用程序的开头)

Thread.currentThread().setContextClassLoader(new ClassLoaderConfig().getClassLoader());

希望此解决方案适用于您!!!

答案 4 :(得分:0)

例如,如果您只是想读取当前的类路径,因为您想启动另一个具有与当前类路径相同的类路径的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)
  )

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

--add-opens java.base/jdk.internal.loader=ALL-UNNAMED

答案 5 :(得分:0)

我找到了,并为我工作。

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

来自网站https://blog.codefx.org/java/java-11-migration-guide/#Casting-To-URL-Class-Loader

答案 6 :(得分:0)

请参阅Edi的解决方案,这对我有用:

public final class IndependentClassLoader extends URLClassLoader {

    private static final ClassLoader INSTANCE = new IndependentClassLoader();

    /**
     * @return instance
     */
    public static ClassLoader getInstance() {

        return INSTANCE;
    }

    private IndependentClassLoader() {

        super(getAppClassLoaderUrls(), null);
    }

    private static URL[] getAppClassLoaderUrls() {

        return getURLs(IndependentClassLoader.class.getClassLoader());
    }

    private static URL[] getURLs(ClassLoader classLoader) {

        Class<?> clazz = classLoader.getClass();

        try {
            Field field = null;
            field = clazz.getDeclaredField("ucp");
            field.setAccessible(true);

            Object urlClassPath = field.get(classLoader);

            Method method = urlClassPath.getClass().getDeclaredMethod("getURLs", new Class[] {});
            method.setAccessible(true);
            URL[] urls = (URL[]) method.invoke(urlClassPath, new Object[] {});

            return urls;

        } catch (Exception e) {
            throw new NestableRuntimeException(e);
        }

    }
}

在Eclipse中运行,您需要将VM参数设置为JUnit启动/调试配置。 通过命令行运行maven有两个选择:

选项1
将以下行添加到pom.xml:

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.16</version>
                <configuration>
                    <argLine>--add-opens java.base/jdk.internal.loader=ALL-UNNAMED</argLine>
                </configuration>
            </plugin>

选项2

运行mvn test -DargLine="-Dsystem.test.property=--add-opens java.base/jdk.internal.loader=ALL-UNNAMED"

答案 7 :(得分:-1)

也有这篇文章对我有所帮助。 我找不到该文章,但...在这里:https://github.com/CGJennings/jar-loader

这里是指南的一部分,发布时有一个罐子,您可以阅读他的指南并进行设置。

我自己尝试过下载包含类文件的jar文件

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;

public final class classname{

    public static void premain(String agentArgs, Instrumentation instrumentation) {
        loadedViaPreMain = true;
        agentmain(agentArgs,instrumentation);
    }

    public final static void addToClassPath(File jarfile)throws IOException{inst.appendToSystemClassLoaderSearch(new JarFile(jarfile));}
    public final static void agentmain(String agentArgs, Instrumentation instrumentation) {
      if (instrumentation == null){throw new NullPointerException("instrumentation");}
      if (inst == null) {inst = instrumentation;}
    }
    private static Instrumentation inst;
    private static boolean loadedViaPreMain = false;
}

我只是自己尝试将这些代码打包为一个包,然后使用-javaagent:plugin ...... jar选项启动应用程序类,然后调用此函数。它不会更改我的类路径。我可能会丢失一些细节。

希望您可以使其正常工作。