目次
・本日の成果・考え
・最後に
本日の成果
前回PGしたブリッジクラスとロジッククラスを使用してウィンドウの表示に加えて、表示したJavaScriptとJavaの連携の動作確認をしたいと思います。
まずは、起動用のソースです。
package app.test;
import app.windowView.window.WindowLogic;
public class WindowLogicTest {
public static void main(String arugs[]) {
WindowLogic logic = new WindowLogic();
try {
logic.execute();
} catch (Exception e) {
throw new IllegalStateException("何かしらの例外発生:"+e);
}
}
}
以下は、今回の動作確認で動作するソースです。
/************
* メソッド名:ウィンドウ表示処理
* 処理内容:JavaFXのApplicationクラスの抽象メソッドの実装。ウィンドウの設定値をセットする。
* @param stage 呼び出し元のlaunchメソッドの第2引数、可変長のString型。:Dtoで注入するので使用しない。
* @return void
* @throws IllegalStateException 実行時の引数のうち整数の項目が整数でなかった場合例外。
/************/
@Override
public void start(Stage stage) throws Exception {
//呼び出しメソッドlaunchの引数受け取り(1,title 2,windWidth 3,windHeight)全てStrin型
List<String> laParams = getParameters().getRaw();
//引数の中身が存在しない場合エラー
if (laParams.size() == 0) {
throw new IllegalStateException("引数に必要な項目がありません。");
}
String title = laParams.get(0);
int width, height;
//整数に格納
try {
width = Integer.parseInt(laParams.get(1));
height = Integer.parseInt(laParams.get(2));
} catch (NumberFormatException e) {
//整数以外例外
throw new IllegalStateException("幅または高さが整数ではありません。:" + e.getMessage());
}
//HTMLファイルのパス取得
String htmlPath = "/resources/window/window_chatBot.html";
URL url = getClass().getResource(htmlPath);
webView.getEngine().getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
//ロジッククラスからコールバック用のオブジェクトがセットされているかチェック。
if (staticCallback != null) {
//セットされていれば、ロジッククラスにWindoViewオブジェクトを渡してブリッジセットメソッド起動。
staticCallback.onWindowSet(this);
} else {
throw new IllegalStateException("BridgeCallbackがセットされていません。");
}
}
});
if (url == null) {
throw new IllegalStateException("HTMLファイルが見つかりません" + htmlPath);
} else {
webView.getEngine().load(url.toExternalForm());
//ウィンドウ表示
Scene scene = new Scene(webView, width, height);
stage.setTitle(title);
stage.setScene(scene);
stage.show();
WindowView.setStaticCallback(new BridgeCallback() {
/**
* JSオブジェクトにブリッジを登録する
* @param view 登録対象のWebViewオブジェクト(JSオブジェクトを内包している)
*/
@Override
public void onWindowSet(WindowView view) {
windowview = view;
webview = windowview.getView();
// JS Bridge登録
JavaBridge bridge = new JavaBridge(this);
// DOMとJSのロードが完了したタイミングでBridge登録とJS呼び出し
try {
JSObject js = (JSObject) webview.getEngine().executeScript("window");
System.out.println("ブリッジ設定メソッド起動");
js.setMember("JavaBridge", bridge);
// JavaScriptの初期化関数を呼び出す(この時点でJavaBridgeは登録済)
js.eval("initChat()");
} catch (Exception e) {
System.out.println("なんかのエラー:" + e);
}
//初期化したUIオブジェクトからラッパーオブジェクトの初期化
wrapper = new WebEngineWrapper(webview.getEngine());
//コントローラークラスの初期化
windowController = new WindowController(wrapper, apiClient, uiRunnable);
}
/**
* コントローラークラスのAPI通信処理起動(ブリッジクラスからの中継)
* @param input ブリッジから受け取ったユーザー入力文字列
*/
@Override
public void onUserInput(String input) {
//API通信処理呼び出し
windowController.onSendMessage(input);
};
});
function initChat() {
if (window.JavaBridge && typeof window.JavaBridge.onUserInput === "function") {
// 例えば初回の案内メッセージを表示するなど
appendMsg("チャットを開始できます。");
} else {
showError("JavaBridgeが正しく設定されていません。");
}
}
package app.windowView.window;
import window_interface.BridgeCallback;
public class JavaBridge {
//ロジッククラスのメソッドを呼び出すための抽象化オブジェクト
private final BridgeCallback bridgecall;
/**
* コンストラクタ。
* @param bc Java側でユーザー入力を処理するロジッククラスの抽象オブジェクト
*/
public JavaBridge(BridgeCallback bc) {
this.bridgecall = bc;
}
/**
* JavaScriptからのメッセージをJava側に送信するためのメソッド。
* @param input ユーザー入力の文字列(nullと空白はフロント側でチェック)
*/
public void sendToJava(String input) {
bridgecall.onUserInput(input);
}
}
処理の流れ
- WindowViewクラスでWebViewのEngineが初期化が正しく行われしだい、ロジッククラスのonWindowSetメソッド起動。
ロジッククラスでは、匿名クラスで実装することでWindowViewからの呼び出しを許可しています。 -
JavaBridge bridge = new JavaBridge(this);
でブリッジクラスを作成し、JSObject js = (JSObject) webview.getEngine().executeScript("window");
でJSObjectにJavaScript側のwindowオブジェクトを取得。 -
js.setMember("JavaBridge", bridge);
でJavaScript側からJavaBridgeのメソッドを呼び出せるように設定。
JS側の呼び出し例:window.JavaBridge.メソッド名
4.if (window.JavaBridge && typeof window.JavaBridge.onUserInput === "function")
で、JavaScriptからみてブリッジが正しくセットされているか判定する。
では、実行してみます。
上記画像では、initChatでのブリッジのセット判定でFalseになっているためエラーメッセージがでています。
調査した限りでは以下が判明しました。
-
if (newState == Worker.State.SUCCEEDED)
はTrueのためウィンドウを表示しているWevEngineは正しく動作している。、 - onWindowSetメソッド内でのブリッジセットの処理をtry-catchした限り例外キャッチしていないため、処理自体はエラーとなっていない。
-
System.out.println("ブリッジ設定メソッド起動");
を仕込んでみると、正しく出力されており、処理も進んでいる。
この時点で以下が原因なのかと考えました
- そもそもブリッジ設定の処理が間違っている
- Java→JavaScriptの呼び出し方が間違っている。
ブリッジの設定
上記のリンクより
JSObjectを使用した設定は
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("app", new JavaApplication());
その後、HTMLページからオブジェクトとメソッドを参照できます。
<a href="" onclick="app.exit()">Click here to exit application</a>
と例が挙げられており、概ね方法は間違っていないと思われます。
呼び出し方
最初にJava→JavaScriptの呼び出しを試しているonWindowSetメソッドの
js.eval("initChat()");
は、特に間違ってなさそう。
そもそもエラーメッセージが出力できている時点で呼び出せている。
であれば、IFの判定かな?
if(window.JavaBridge && typeof window.JavaBridge.onUserInput === "function")
JavaBridge.onUserInput ??
ブリッジクラスにonUserInputなんて処理なかったよね。
直接ロジッククラスのメソッドを呼び出していますね。
if (window.JavaBridge && typeof window.JavaBridge.sendToJava === "function")
に修正。
再度動作チェック
ついでにinitChatを修正
※appendMsgだと、API通信処理を呼び出した時に、起動メッセージの欄に受信したチャンクを追加してしまうため、completeMsgでこのメッセージ欄への追加をできないようにします。
function initChat() {
if (window.JavaBridge && typeof window.JavaBridge.sendToJava === "function") {
// 例えば初回の案内メッセージを表示するなど
completeMsg();
} else {
showError("JavaBridgeが正しく設定されていません。");
}
}
最後に
ここまでお付き合いありがとうございます。
これで、Java→JavaScriptの呼び出しのための実装ができ、各UTとLTを残すのみとなりました。
今週はこの見落としでかなり時間を使ってしまっていることと、資格勉強が全く進んでいない状況のため、もしかしたらAPI通信の動作確認をしたら一旦終了するかもです。
というのも、今年の冬くらいにJavaではなくAWSを使用している案件へのシフトを希望しており、そのために資格+AWSハンズオンを記事にしてPRする必要がありそうだからです。