为什么在Java中这么难做到这一点?如果您希望拥有任何类型的模块系统,您需要能够动态加载JAR文件。有人告诉我,有一种方法可以通过编写自己的ClassLoader来实现,但这需要做很多工作,因为(至少在我看来)应该像调用以JAR文件作为参数的方法一样简单。

对实现这一功能的简单代码有什么建议吗?


当前回答

困难的原因在于安全。类加载器是不可变的;您不应该在运行时随意地向它添加类。我很惊讶它能与系统类加载器一起工作。下面是如何创建你自己的子类加载器:

URLClassLoader child = new URLClassLoader(
        new URL[] {myJar.toURI().toURL()},
        this.getClass().getClassLoader()
);
Class classToLoad = Class.forName("com.MyClass", true, child);
Method method = classToLoad.getDeclaredMethod("myMethod");
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance);

很痛苦,但事实就是这样。

其他回答

如果你在Android上工作,下面的代码可以工作:

String jarFile = "path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(jarFile, "/data/data/" + context.getPackageName() + "/", null, getClass().getClassLoader());
Class<?> myClass = classLoader.loadClass("MyClass");

以防将来有人搜索这个,这种方法对我来说适用于OpenJDK 13.0.2。

我有许多需要在运行时动态实例化的类,每个类都可能具有不同的类路径。

在这段代码中,我已经有了一个名为pack的对象,它保存了一些关于我试图加载的类的元数据。getObjectFile()方法返回类的类文件的位置。getObjectRootPath()方法返回bin/目录的路径,其中包含我试图实例化的类的类文件。getLibPath()方法返回一个目录的路径,该目录包含构成该类所在模块类路径的jar文件。

File object = new File(pack.getObjectFile()).getAbsoluteFile();
Object packObject;
try {
    URLClassLoader classloader;

    List<URL> classpath = new ArrayList<>();
    classpath.add(new File(pack.getObjectRootPath()).toURI().toURL());
    for (File jar : FileUtils.listFiles(new File(pack.getLibPath()), new String[] {"jar"}, true)) {
        classpath.add(jar.toURI().toURL());
    }
    classloader = new URLClassLoader(classpath.toArray(new URL[] {}));

    Class<?> clazz = classloader.loadClass(object.getName());
    packObject = clazz.getDeclaredConstructor().newInstance();

} catch (Exception e) {
    e.printStackTrace();
    throw e;
}
return packObject;

我以前使用Maven依赖项:org.xeustechnologies:jcl-core:2.8来做这件事,但在JDK 1.8之后,它有时会冻结,并且永远不会返回在Reference::waitForReferencePendingList()中卡住“等待引用”。

我还保留了类加载器的映射,以便在我试图实例化的类与我已经实例化的类在同一个模块中时可以重用它们,我建议这样做。

我个人认为java.util.ServiceLoader做得很好。你可以在这里得到一个例子。

下面的解决方案有点笨拙,因为它使用反射来绕过封装,但它工作得完美无缺:

File file = ...
URL url = file.toURI().toURL();

URLClassLoader classLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, url);

下面是Allain的方法的一个快速变通方法,使其与新版本的Java兼容:

ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try {
    Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class);
    method.setAccessible(true);
    method.invoke(classLoader, new File(jarPath).toURI().toURL());
} catch (NoSuchMethodException e) {
    Method method = classLoader.getClass()
            .getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
    method.setAccessible(true);
    method.invoke(classLoader, jarPath);
}

请注意,它依赖于特定JVM内部实现的知识,因此它不是理想的,也不是通用的解决方案。但是,如果您知道您将使用标准的OpenJDK或Oracle JVM,这是一种快速而简单的解决方法。在将来发布新的JVM版本时,它也可能会崩溃,所以您需要记住这一点。