如何在加载 class 之前修改它?

How do I modify a class before it's been loaded?

我需要对 JavaFX WebView 进行手术 class to make it render even when not visible. In my quest to achieve this I found Javassist,但是当我尝试使用它时,出现此错误:

java.lang.IllegalArgumentException: Can not set javafx.scene.web.WebView field sample.Controller.webView to javafx.scene.web.WebView

我想那是因为成员是在修改之前定义的?我不确定。目前,我没有修改 class 中的任何内容,只是加载、解冻(?)并保存它,使用代码:

CtClass webViewClass = ClassPool.getDefault().get("javafx.scene.web.WebView");
webViewClass.defrost();
webViewClass.toClass();

这是我的最小可重现示例。首先,Main.java:

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

public class Main extends Application {
    public static void main(String[] args) {
        try {
            CtClass webViewClass = ClassPool.getDefault().get("javafx.scene.web.WebView");
            webViewClass.defrost();
            webViewClass.toClass();
        } catch (NotFoundException | CannotCompileException e) {
            e.printStackTrace();
        }
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setScene(new Scene(root, 800, 600));
        primaryStage.show();
    }
}

这些是 Controller.java 文件的内容:

package sample;

import javafx.fxml.FXML;
import javafx.scene.web.WebView;

public class Controller {
    @FXML
    private WebView webView;

    @FXML
    private void initialize() {
        webView.getEngine().load("http://whosebug.com");
    }
}

这是视图,sample.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.web.WebView?>
<AnchorPane xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="sample.Controller">
    <WebView fx:id="webView" minHeight="-Infinity" minWidth="-Infinity"/>
</AnchorPane>

完整的例外是:

Exception in Application start method
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication4(LauncherImpl.java:182)
    at java.lang.Thread.run(Thread.java:748)
Caused by: javafx.fxml.LoadException: 
/C:/Users/pupeno/Documents/Dashman/code/experiments/webviewwoes/out/production/webviewwoes/sample/sample.fxml:8

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2579)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3214)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
    at sample.Main.start(Main.java:27)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication11(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait4(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null2(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater3(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null7(WinApplication.java:177)
    ... 1 more
Caused by: java.lang.IllegalArgumentException: Can not set javafx.scene.web.WebView field sample.Controller.webView to javafx.scene.web.WebView
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
    at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:81)
    at java.lang.reflect.Field.set(Field.java:764)
    at javafx.fxml.FXMLLoader.injectFields(FXMLLoader.java:1163)
    at javafx.fxml.FXMLLoader.access00(FXMLLoader.java:103)
    at javafx.fxml.FXMLLoader$ValueElement.processValue(FXMLLoader.java:857)
    at javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:765)
    at javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2823)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2532)
    ... 17 more
Exception running application sample.Main

Process finished with exit code 1

很可能有两个版本的 Webview 由两个不同的类加载器加载,至少我希望只有在这种情况下才会出现这样的异常。

注意 javassist 的这段话 tutorial:

If the program is running on some application server such as JBoss and Tomcat, the context class loader used by toClass() might be inappropriate. In this case, you would see an unexpected ClassCastException. To avoid this exception, you must explicitly give an appropriate class loader to toClass(). For example, if bean is your session bean object, then the following code:

CtClass cc = ...; Class c = cc.toClass(bean.getClass().getClassLoader()); would work. You should give toClass() the class loader that has loaded your program (in the above example, the class of the bean object).

调用 toClass() 使线程的上下文 class 加载程序加载 class。我假设当 FXMLLoader 加载视图时,它会使用不同的 classloader 加载 classes,并且不知何故事情会混淆,因此您最终会遇到此异常。另一个可能的结果是一切加载正常,但您的修改不起作用,因为 FXMLLoader 加载未修改的 classes.

做这样的事情:

ClassPool classPool = ClassPool.getDefault();
CtClass webViewClass = classPool.get("javafx.scene.web.WebView");
webViewClass.defrost();
classPool.toClass(webViewClass, FXMLLoader.class.getClassLoader(), null);