问题
我花了几个小时试图确定我的分布式代码失败的原因,但是在使用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不是功能...... “)。
答案 0 :(得分:5)
解决方案
我发现声明一个全局变量来保存JavaLink类的一个实例并将其作为参数传递给setMember()方法修复它,以便应用程序现在既可以在IDE中运行,也可以在独立的JAR中运行: / p>
JavaLink jl;
...
jl = new JavaLink();
//replace anonymous instantiation of JavaLink with global variable
jsObj.setMember("javaLink", jl);
为什么!?的
我猜这与垃圾收集有关,除非你通过声明一个全局变量来强制它,否则JVM不会保留对JavaLink的引用。还有其他想法吗?
答案 1 :(得分: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();
后,更新会继续显示在控制台中。
答案 2 :(得分: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);
然后,从Java语言中,我将类方法称为:
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
最后,为了从Java嵌套类中调用方法,就需要对象重新分配,就像这样(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();
我希望这可以帮助面临同样问题的人们。我正在将Java JDK 1.8与IntelliJIDEA一起使用。