Blog 版:http://blog.dontcareabout.us/2014/04/js-java-static-method.html
BBS 版以 markdown 語法撰寫
______________________________________________________________________
內容有點無腦,先把結論寫在前面:
> 外部 JS(官方文件稱為「handwritten JS」,
> 其實不同 GWT Module 就滿足這個條件)要呼叫 Java 的 static method,
> 必須先透過 JSNI 設定 `$wnd.methodName =
> @fooPackage.FooClass::javaMethodName(*)`,
> 後續使用 `$wnd.methodName()` 來達到目的。
>
> 關鍵在於 JSNI 中:
>
> * `javaMethodName()` 後頭不需再加 `()`。
> * `javaMethodName()` 的參數某些情況下可以省略 field descriptor,
> 直接用 `*` 代替。
update:感謝 darkk6(ptt.cc)提醒,讓我發現我不但死腦筋,
而且還少測了一種寫法... 所以文章就要重新翻修了 (艸
前天被問到一個問題:
「同一個 host page 上兩個不同 GWT Module 要怎麼溝通?」
我原本以為這不會是問題,
結果回到家實際測了一下才發現根本不是這麼一回事 [死]。
一言以蔽之,可以用這個 [stackoverflow][so1] 來解釋。
上頭發問的內容大抵上就是我原本的想法:用 event bus 來解,
結果是不可行的,不過那不是這篇文章的重點 XD
[so1]: http://stackoverflow.com/questions/17273572/
how-to-communicate-two-modules-in-gwt?answertab=votes#tab-top
為了保險起見實際測試了一下解答的 code,然後就發現根本行不通:
private native static exportMyJavaMethod() /*-{
$wnd.myJavaMethod = @my.package.module1.MyClass1::myJavaMethod;
}-*/;
那個 `myJavaMethod` 沒給參數的 field descriptor,
GPE 就報 syntax error、development mode 也一樣炸。
回頭去確認[官方文件],沒想到[官方文件]的程式碼一樣微妙......
package mypackage;
public MyUtilityClass{
public static int computeLoanInterest(
int amt, float interestRate, int term) { ... }
public static native void exportStaticMethod() /*-{
$wnd.computeLoanInterest = $entry(
@mypackage.MyUtilityClass::computeLoanInterest(IFI)
);
}-*/;
}
我不太確定那個 `IFI` 是啥意思 or 啥縮寫...... =="
[官方文件]: http://www.gwtproject.org/doc/latest/
DevGuideCodingBasicsJSNI.html#calling
好,不管這些,自己重新寫一個程式測試這個部份:
package foo.client;
//import 略
public class FooEP implements EntryPoint {
@Override
public void onModuleLoad() {
exportJsMethod();
callFooMethod("...");
}
static void javaMethod(String arg1) {
Window.alert("javaMethod : " + arg1);
}
native static void exportJsMethod() /*-{
$wnd.fooMethod = @foo.client.FooEP::javaMethod
(Ljava/lang/String;)();
}-*/;
native static void callFooMethod(String message) /*-{
$wnd.fooMethod(message);
}-*/;
}
結果在 development mode 執行就炸錯誤,主要錯誤訊息是
com.google.gwt.core.client.JavaScriptException:
(TypeError) @foo.client.FooEP::callFooMethod()([]):
undefined is not a function
其實在這錯誤訊息之前就有兩個地方散發出怪味道:
1. `$wnd.fooMethod = @foo.client.FooEP::javaMethod
(Ljava/lang/String;)();`,
為什麼沒有實際給 `javaMethod()` 參數?
等等,這個時間點給他什麼都不對啊?
2. 忽略 1,為什麼會先跳出 alert 視窗顯示 `javaMethod : null`,
然後才炸錯誤?
如果也在 `callFooMethod()` 裡頭先卡一行 `$wnd.alert("WTF?")`,
會發現顯示 `WTF?` 的 alert 視窗還是會出現,
表示 `callFooMethod()` 有正確被執行到,炸的是 `$wnd.fooMethod()`。
整個看起來,事情好像就有頭緒了。
其實 `exportJsMethod()` 的 `$wnd.fooMethod`
根本沒有正確 assign 成 `FooEP.javaMethod()`,
而是 assign 成 `FooEP.javaMethod()` 的回傳值──根本沒這玩意,
所以到 `callFooMethod()` 的時候當然就炸了。
原先我一直死腦筋用 Java 的角度去看,
後來換成 JS 的角度去看其實這樣結果很正常。
在 JS 當中一個變數可以代表一個數值、
也可以代表一個 function / method,
如果程式中要執行該 function / method,那就是加上 `()`,
例如在 Chrome console 輸入 `alert`
會得到 `function alert() { [native code] }`,
而 `alert("WTF")` 才會真正跳出 alert 視窗。
在上頭的 case 當中,我只是要把 $wnd.fooMethod 指定為一段程式碼,
根本沒有要執行它的意思,所以加上 `()` 根本就是多餘且錯誤阿...

)
所以拿掉後頭的空括號,一切就正常了(之前怎麼沒想過阿阿阿阿 [核爆]),
[官方文件]的那個 `IFI`,
darkk6 猜測是 `int`、`float`、`int` 的縮寫,
如今(修正完之後)回頭看也很合理了 [死]。
最後不小心看到另一個 [stackoverflow][so2],
發現還有一招裏技可以用,
就是直接給 `*` 號,省去打一堆 field descriptor:
native static void exportJsMethod() /*-{
$wnd.fooMethod = @foo.client.FooEP::javaMethod(*);
}-*/;
這有個前提,就是 `FooEP` 中只能有一個 `javaMethod()`,
否則就會炸錯誤:也就是說,
如果你有兩個以上同名的 method,那還是得乖乖寫 field descriptor。
另外,在 JSNI 中呼叫 `$wnd.fooMethod()` 時已經是純粹 JS 了,
所以即使你多傳參數、或是少傳參數也不會怎麼樣......
至少 GPE 跟 browser 都沒有特別反應。
native static void callFooMethod(String message) /*-{
$wnd.fooMethod();
$wnd.fooMethod(message, "又多餘了");
}-*/;
[so2]: http://stackoverflow.com/questions/16080099/calling-gwt-java-function-from-javascript?rq=1
好了,事實證明 GWT 文件也沒寫得那麼好,
除了上次那個很隱晦不想讓人知道的 [AutoBean] 之外,
又遇到了一個謎樣裏技。還是說其實官方文件有,只是我沒找到呢...... [淚目]
[AutoBean]: http://blog.dontcareabout.us/2013/12/gwt-autobean.html
====
再次感謝 darkk6 Orz