4

问题

我花了几个小时试图确定为什么我的分布式代码会失败,但是在使用 IDE (NetBeans) 进行调试时我的源代码可以正常工作。我找到了解决方案,并发布以帮助可能有类似问题的其他人。顺便说一句:我是一名自学成才的程序员,可能缺少一些基本概念——请随时教育我。

背景资料

在 JavaFX 应用程序中使用 WebView 控件,我从 html 文件加载网页。我想使用 JavaScript 来处理 HTML 方面的事情,但我还需要在 Java 和 JavaScript 之间自由传递信息(双向)。非常适合使用 WebEngine.executeScript() 方法进行 Java 发起的传输,并使用 Java 中的 JSObject.setMember() 为 JavaScript 启动向 Java 传输信息的方式。

设置链接(这种方式稍后会中断):

/*Simple example class that gives method for 
JavaScript to send text to Java debugger console*/
public static class JavaLink {
    public void showMsg(String msg) {
        System.out.println(msg);
    }
}

...

/*This can be added in the initialize() method of  
the FXML controller with a reference to the WebEngine*/
public void initialize(URL url, ResourceBundle rb) {
    webE = webView.getEngine();

    //Retrieve a reference to the JavaScript window object
    JSObject jsObj = (JSObject)webE.executeScript("window");
    jsObj.setMember("javaLink", new JavaLink());
    /*Now in our JavaScript code we can type javaLink.showMsg("Hello!");
    which will send 'Hello!' to the debugger console*/
}

在分发它并尝试运行 JAR 文件之前,上面的代码将运行良好。经过数小时的搜索和测试不同的调整后,我终于将问题缩小到 JavaLink 对象本身(我最终了解到您可以在 JavaScript 中使用 try-catch 块,这使我能够捕获错误:“ TypeError: showMsg is not a function. .. ”)。

4

3 回答 3

6

解决方案

我发现声明一个全局变量来保存 JavaLink 类的一个实例并将其作为参数传递给 setMember() 方法可以修复它,这样应用程序现在既可以在 IDE 中运行,也可以在独立 JAR 中运行:

JavaLink jl;

...

    jl = new JavaLink();

    //replace anonymous instantiation of JavaLink with global variable
    jsObj.setMember("javaLink", jl); 

为什么!?

我猜这与垃圾收集有关,并且 JVM 不会保留对 JavaLink 的引用,除非您通过声明全局变量来强制它。还有其他想法吗?

于 2017-03-08T00:49:38.833 回答
2

只是为了演示@DatuPuti 的答案中假设的假设似乎是正确的。这是一个快速测试。按下 HTML 按钮会增加 HTML 页面中的计数器,并且还会输出到系统控制台。通过按下“GC”按钮强制进行垃圾收集后,系统控制台的更新将停止:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;

public class WebViewCallbackGCTest extends Application {

    private final String HTML = 
              "<html>"
            + "  <head>"
            + "    <script>"
            + "    var count = 0 ;"
            + "    function doCallback() {"
            + "      count++ ;"
            + "      javaLink.showMsg('Hello world '+count);"
            + "      document.getElementById('test').innerHTML='test '+count;"
            + "    }"
            + "    </script>"
            + "  </head>"
            + "  <body>"
            + "    <div>"
            + "      <button onclick='doCallback()'>Call Java</button>"
            + "    </div>"
            + "    <div id='test'></div>"
            + "  </body>"
            + "</html>" ;

    @Override
    public void start(Stage primaryStage) {
        WebView webView = new WebView();
        webView.getEngine().loadContent(HTML);

        JSObject js = (JSObject) webView.getEngine().executeScript("window");
        js.setMember("javaLink", new JavaLink());

        Button gc = new Button("GC");
        gc.setOnAction(e -> System.gc());

        BorderPane root = new BorderPane(webView);
        BorderPane.setAlignment(gc, Pos.CENTER);
        BorderPane.setMargin(gc, new Insets(5));
        root.setBottom(gc);
        primaryStage.setScene(new Scene(root, 400, 400));
        primaryStage.show();
    }

    public static class JavaLink {
        public void showMsg(String msg) {
            System.out.println(msg);
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

当然,如另一个答案中所述,如果您声明一个实例变量

private JavaLink jl = new JavaLink();

并更换

js.setMember("javaLink", new JavaLink());

js.setMember("javaLink", jl);

问题已修复,调用后更新继续出现在控制台中System.gc();

于 2017-03-08T01:15:02.733 回答
0

@DatuPuti 的解决方案工作正常,但部分工作。在我的例子中,我在 JavaLink 类中有多个嵌套类。在做@DatuPuti 暴露所有嵌套类链接断开的操作之前,在应用暴露的解决方案之后,只有被调用的类是保持链接的类,其余的仍然断开。这是我的 JavaLink 类的模型:

public class JavaLink{

    public NestedClass1 ClassName1;
    public NestedClass2 ClassName2;

    //Constructor
    public JavaLink(){
        ClassName1 = new NestedClass1();
        ClassName2 = new NestedClass2();
    }

    //Nested classes being called from Javascript
    public class NestedClass1(){}
    public class NestedClass2(){}

}

链接将像这样创建:

JavaLink javaLink;
...

    javaLink = new JavaLink();
    jsObject.setMember("JavaLink", javaLink);

然后,从 Javascript 我调用类方法是这样的:

JavaLink.ClassName1.method();
JavaLink.ClassName2.method();

问题来了:当崩溃发生调用ClassName1方法时,ClassName2取消链接并且不再使用 Javascript 可用。如果我在调用ClassName2方法时发生崩溃,也会发生同样的情况。

对我有用的解决方案(除了暴露的解决方案):

除了JavaLink在可能的更高范围内声明外,声明所有嵌套类将使它们保持链接。例如(与示例类模型保持相同的引用):

JavaLink javaLink;
JavaLink.NestedClass1 nestedClass1;
JavaLink.NestedClass2 nestedClass2;

    javaLink = new JavaLink();
    nestedClass1 = javaLink.new NestedClass1();
    nestedClass2 = javaLink.new NestedClass2();

    //Creating an Object array to store classes instances
    Object[] javaLinkClasses = new Object[2];
    javaLinkClasses[0] = nestedClass1;
    javaLinkClasses[1] = nestedClass2;
    jsObject.setMember("JavaLink", javaLinkClasses); //Setting member as an array

最后,为了在 Javascript 中调用嵌套类的方法,需要重新分配对象,就像这样(Javascript):

JavaLink.NestedClass1 = JavaLink[0];
JavaLink.NestedClass2 = JavaLink[1];

//Now we are able to call again methods from nested classes without them unlinking
JavaLink.NestedClass1.method();

我希望这可以帮助面临同样问题的人。我正在使用带有 IntelliJIDEA 的 Java JDK 1.8。

于 2019-07-29T17:09:32.480 回答