Pablo Fernandez
Pablo Fernandez

Reputation: 287410

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

I'm in need of doing surgery to the JavaFX WebView class to make it render even when not visible. In my quest to achieve this I found Javassist, but when I try to use it, I get this error:

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

I think that's because the member was defined before it got modified? I'm not sure. At the moment, I'm not modifying anything in the class, just loading, defrosting it (?) and saving it, by using the code:

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

This is my minimum reproducible example. First, 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();
    }
}

and these are the contents of the Controller.java file:

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://stackoverflow.com");
    }
}

and this is the view, 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>

The full exception is:

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$launchApplication$154(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$launchApplication1$161(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(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$null$147(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.access$1600(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

Upvotes: 4

Views: 704

Answers (1)

kutschkem
kutschkem

Reputation: 8163

Most likely there are two versions of your Webview loaded by two different ClassLoaders, at least I would expect such an exception only in that case.

Note this paragraph from the 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).

Calling toClass() makes the context class loader of the thread load the class. I assume when the FXMLLoader loads the view, it loads the classes with a different classloader, and somehow things get mixed up so that you end up with this exception. Another possible outcome would have been that everything loads fine, but your modifications don't work because the FXMLLoader loads unmodified classes.

Do something like this:

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

Upvotes: 4

Related Questions