java - (JDK 11) 通过依赖库的反射运行 JAR 存档

标签 java javafx kotlin

注意:我对 Java 9 中引入的 Module 系统经验很少。

我有一个 Java 进程,它应该通过反射加载和执行另一个 Jar 文件来运行它。 请注意,这两个 Jar 文件都依赖于 JavaFX 框架,该框架在版本 11 中与 JDK 分离,因此可能会被第二次加载。

这里是适用于 JDK 版本 8 的原始版本:

val ideFile = File(binFolder, "Sk-IDE.jar")
val classLoader = ClassLoader.getSystemClassLoader() as URLClassLoader
val method = URLClassLoader::class.java.getDeclaredMethod("addURL", URL::class.java)
method.isAccessible = true
method.invoke(classLoader, ideFile.toURI().toURL())
val coreManager = Class.forName("com.skide.CoreManager")
val instance = coreManager.newInstance()
coreManager.getDeclaredMethod("bootstrap", Array<String>::class.java).invoke(instance, State.args)

(对 addURL 的反射调用是必要的,否则加载器 Jar 无法加载其 fxml 文件)

此方法在 JDK 11 上抛出此异常:

java.lang.ClassCastException: class jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to class java.net.URLClassLoader (jdk.internal.loader.ClassLoaders$AppClassLoader and java.net.URLClassLoader are in module java.base of loader 'bootstrap')

创建新的URLClassLoader的方法:

val child = URLClassLoader(arrayOf(URL(ideFile.toURI().toURL().toString())), Installer::class.java.classLoader)
val coreManager = Class.forName("com.skide.CoreManager", true, child)
val instance = coreManager.newInstance()
Platform.runLater {
    coreManager.getDeclaredMethod("bootstrap", Array<String>::class.java).invoke(instance, State.args)
}

抛出与 JavaFX 相关的异常:

Exception in thread "JavaFX Application Thread" [20.11.2018 20:13:41 | ERROR] java.security.PrivilegedActionException: java.lang.reflect.InvocationTargetException
[20.11.2018 20:13:41 | ERROR]   at java.base/java.security.AccessController.doPrivileged(Native Method)
[20.11.2018 20:13:41 | ERROR]   at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
[20.11.2018 20:13:41 | ERROR]   at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
[20.11.2018 20:13:41 | ERROR]   at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
[20.11.2018 20:13:41 | ERROR]   at com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
[20.11.2018 20:13:41 | ERROR]   at java.base/java.lang.Thread.run(Thread.java:834)
[20.11.2018 20:13:41 | ERROR] Caused by: java.lang.reflect.InvocationTargetException
[20.11.2018 20:13:41 | ERROR]   at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[20.11.2018 20:13:41 | ERROR]   at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[20.11.2018 20:13:41 | ERROR]   at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[20.11.2018 20:13:41 | ERROR]   at java.base/java.lang.reflect.Method.invoke(Method.java:566)
[20.11.2018 20:13:41 | ERROR]   at com.skide.installer.Installer$start$2$1$1.run(Installer.kt:249)
[20.11.2018 20:13:41 | ERROR]   at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
[20.11.2018 20:13:41 | ERROR]   ... 6 more
[20.11.2018 20:13:41 | ERROR] Caused by: javafx.fxml.LoadException: 
unknown path:9

[20.11.2018 20:13:41 | ERROR]   at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2625)
[20.11.2018 20:13:41 | ERROR]   at javafx.fxml.FXMLLoader.access$700(FXMLLoader.java:105)
[20.11.2018 20:13:41 | ERROR]   at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:930)
[20.11.2018 20:13:41 | ERROR]   at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:980)
[20.11.2018 20:13:41 | ERROR]   at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:227)
[20.11.2018 20:13:41 | ERROR]   at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:752)
[20.11.2018 20:13:41 | ERROR]   at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2722)
[20.11.2018 20:13:41 | ERROR]   at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2552)
[20.11.2018 20:13:41 | ERROR]   at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2450)
[20.11.2018 20:13:41 | ERROR]   at com.skide.CoreManager$bootstrap$1.invoke(CoreManager.kt:47)
[20.11.2018 20:13:41 | ERROR]   at com.skide.CoreManager$bootstrap$1.invoke(CoreManager.kt:24)
[20.11.2018 20:13:41 | ERROR]   at com.skide.CoreManager.bootstrap(CoreManager.kt:136)
[20.11.2018 20:13:41 | ERROR]   ... 12 more
[20.11.2018 20:13:41 | ERROR] Caused by: java.lang.ClassNotFoundException: com.skide.gui.controllers.SplashGuiController
[20.11.2018 20:13:41 | ERROR]   at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
[20.11.2018 20:13:41 | ERROR]   at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
[20.11.2018 20:13:41 | ERROR]   at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
[20.11.2018 20:13:41 | ERROR]   at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:928)
[20.11.2018 20:13:41 | ERROR]   ... 21 more

解决这个问题的最佳方法是什么?

问候, 丽兹3

编辑

在 xtratic 的帮助下,解决方案实际上很简单:

我只是将 child URLClassloader 添加到 CoreManager#bootstrap 反射调用,即 CoreManager(加载的 jar)的引导方法,而不是将该 URLClassloader 传递给所有 FXML 加载器。

最佳答案

当您使用新的 URLClassLoader 时,似乎正在调用方法 但抛出一个 javafx.fxml.LoadExceptionPrivilegedActionExceptionInvocationTargetException 包装。

即使您使用自己的 URLClassloader 加载您的类,它看起来像 FXML 解析器正在尝试使用内置类加载器加载 FX bean,它不知道FXML.

您需要让 FXMLLoader 使用包含 fxml 引用的所有类的正确类加载器。阅读 FXMLLoader 的源代码,了解它如何处理类加载。如果可能,您可能需要修改执行 fxml 加载的库。查看 FXMLLoader.setClassLoader(urlClassLoader) 或可能设置 SecurityManager 以便 FXMLLoader 将使用调用类加载器。


FXMLLoader.java

/**
 * Returns the classloader used by this serializer.
 * @since JavaFX 2.1
 */
@CallerSensitive
public ClassLoader getClassLoader() {
    if (classLoader == null) {
        final SecurityManager sm = System.getSecurityManager();
        final Class caller = (sm != null) ?
                Reflection.getCallerClass() :
                null;
        return getDefaultClassLoader(caller);
    }
    return classLoader;
}

private static ClassLoader getDefaultClassLoader(Class caller) {
    if (defaultClassLoader == null) {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            final ClassLoader callerClassLoader = (caller != null) ?
                    caller.getClassLoader() :
                    null;
            if (needsClassLoaderPermissionCheck(callerClassLoader, FXMLLoader.class.getClassLoader())) {
                sm.checkPermission(GET_CLASSLOADER_PERMISSION);
            }
        }
        return Thread.currentThread().getContextClassLoader();
    }
    return defaultClassLoader;
}

我猜测 Java11 可能已经改变了它的类加载器。

参见 this link (下面粘贴内容)

Casting To URL Class Loader
Java 9 and the module system improved the platform’s class loading strategy, which is implemented in a new type and in Java 11 the application class loader is of that type. That means it is not a URLClassLoader, anymore, so the occasional (URLClassLoader) getClass().getClassLoader() or (URLClassLoader) ClassLoader.getSystemClassLoader() sequences will no longer execute. This is another typical example where Java 11 is backwards compatible in the strict sense (because that it’s a URLCassLoader was never specified) but which can nonetheless cause migration challenges.

Symptoms
This one is very obvious. You’ll get a ClassCastException complaining that the new AppClassLoader is no URLClassLoader:

Exception in thread "main" java.lang.ClassCastException:
java.base/jdk.internal.loader.ClassLoaders$AppClassLoader
cannot be cast to java.base/java.net.URLClassLoader
    at monitor.Main.logClassPathContent(Main.java:46)
    at monitor.Main.main(Main.java:28)

Fixes
The class loader was probably cast to access methods specific to URLClassLoader. If so, you might have to face some serious changes.
...
If you’ve used the URLClassLoader to dynamically load user provided code (for example as part of a plugin infrastructure) by appending to the class path, then you have to find a new way to do that as it can not be done with Java 11. You should instead consider creating a new class loader for that. This has the added advantage that you’ll be able to get rid of the new classes as they are not loaded into the application class loader. You should also read up on layers – they give you a clean abstraction for loading an entirely new module graph.

关于java - (JDK 11) 通过依赖库的反射运行 JAR 存档,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53398392/

相关文章:

java - 如何在android中比较两个日期和时间

java - 从 JavaFX : Can not set javafx. scene.control.Label 字段 application.SceneController.myLabel 开始到 javafx.scene.text.Text

java - 如何不断生成矩形并更新它们AnimationTimer?

JAVAFX:阻止用户打开新窗口,直到他关闭第一个窗口

macos - 以编程方式更改应用程序 Dock 图标 javaFX

android - Android 中使用 Kotlin 的简单 HTTP 请求示例

enums - 如何跨一组枚举类编写可重用的字符串到枚举值的转换? ( Kotlin )

android - 在 Kotlin Android 中处理多个函数中的可变变量

java - 在我的 session 中添加 cookie 时遇到问题

java - 用整数计算