很久以前听说过一种写法
比如写个安卓程序,刚学几天还不太会写安卓,就把每个页面都写成本地的html,再用WebView嵌套。
图方便,运行效率低,用户体验差,曾几何时,这是一种很糟嫌弃的写法
没想到,等我学JavaFX,学到吐血的时候,发现JavaFX也可以WebView嵌套Html
比起百度半天答非所问 全英文文档啃到吐血 html我不到5分钟就能写一个页面 一不小心真香了属于是
`嘲笑WebView` => `质疑WebView` => `理解WebView` => `成为WebView`

## 折腾
最终目的是希望写个Demo
前端展示全部用本地的Html文件
后端逻辑控制全部用Java和JS交互实现
以达到一个节省学习JavaFX前端组件的目的
环境大致是这样
系统 `LinuxDeepin 20.9`
IDE `IntelliJ IDEA 2023.1.2 (Ultimate Edition)`
环境 `OpenJDK 11.0.9`
**新建JavaFX项目**
上一篇文章详细讲过了,这里略过
地址: [2023 IDEA 开发桌面图形界面程序 JavaFX 并导出 Jar 支持多系统](https://zzzmh.cn/post/eee76yv5u9e48dkarmzymnfryohapwm0)
**稍作修改**
由于不用写JavaFX的组件模块,直接简化开发,删除了fxml和相关依赖代码,全部用Java代码即可搞定
maven核心依赖是这三个
`pom.xml`
```xml
org.openjfx
javafx-controls
17.0.6
org.openjfx
javafx-web
17.0.6
手动引入 `javafx.web` 到模块化
`module-info.java`
```java
module com.zzzmh.jfx {
requires javafx.controls;
// 手动引入到模块化
requires javafx.web;
exports com.zzzmh.jfx;
删除了不需要的文件
最终目录如下
```treeview
JFX-WebView-Demo/
|-- src/
| |-- main/
| | |-- java/
| | | |-- com.zzzmh.jfx/
| | | | |-- controller/
| | | | | |-- Controller.java
| | | | |-- App.java
| | | |-- module-info.java
| | |-- resources/
| | | |-- com.zzzmh.jfk/
| | | | |-- index/
| | | | | |-- index.html
`-- pom.xml
`index.html` 先随便写点东西占位
```html
JavaFX WebView Demo
测试
最后改下启动类
把fxml去掉,改为WebView作为最外层Panel
WebView直接加载本地的index.html显示
```java
package com.zzzmh.jfx;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage stage) {
WebView webView = new WebView();
webView.getEngine().load(getClass().getResource(
"/com/zzzmh/jfx/html/index.html").toExternalForm());
Scene scene = new Scene(webView, 600, 400);
stage.setTitle("JavaFX WebView Demo");
stage.setScene(scene);
stage.show();
public static void main(String[] args) {
launch();
执行`App.java` `main`方法
一次跑通!

既然能用html作为前端显示,那写个页面就是分分钟的事情了
目前只剩下最后一个问题
不像node js可以直接调用系统api
这里的js只是webview内部的脚本
连接系统的是靠java代码
如果用传统方法,java写个api接口,js调接口,相当于是机关枪打蚊子
所以下一步的思路就是js直接调用java方法,实现交互逻辑
**JS调用Java方法 前后端交互**
参考了这几个链接
[JavaFX学习之在javascript中调用javaFX中提供的java方法](https://blog.csdn.net/zy103118/article/details/127425406)
[JavaFx Webview 与js(vue)交互](https://blog.csdn.net/weixin_44517645/article/details/128180261)
大致代码如下
`Controller.java`
```java
package com.zzzmh.jfx.controller;
public class Controller {
* 获取后端数据
public String getData() {
// 这里假装去数据库查询了一套json数据
return "{\"name\":\"张三\",\"age\":9}";
`App.java`
```java
package com.zzzmh.jfx;
import com.zzzmh.jfx.controller.Controller;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class App extends Application {
@Override
public void start(Stage stage) {
WebView webView = new WebView();
WebEngine engine = webView.getEngine();
// 注入方法
engine.getLoadWorker().stateProperty().addListener(
(ObservableValue ov, Worker.State oldState, Worker.State newState) -> {
if (newState == Worker.State.SUCCEEDED) {
// 获取JS的window对象
JSObject window = (JSObject) engine.executeScript("window");
// 讲controller注入到window对象中
window.setMember("controller", new Controller());
// 加载页面
engine.load(Controller.class.getResource(
"/com/zzzmh/jfx/html/index.html").toExternalForm());
Scene scene = new Scene(webView, 600, 400);
stage.setTitle("JavaFX WebView Demo");
stage.setScene(scene);
stage.show();
public static void main(String[] args) {
launch();
`index.html`
```html
JavaFX WebView Demo
功能测试
然后就遇到问题了
无论怎么点`获取数据`按钮都没有反应
后端的 `public String getData()` 方法里打断点,也没任何反应
关键看不到报错信息,Java的控制台没报错,前端JS的控制台看不到
百度半天也没查到有用信息
于是没办法我只能用一个笨办法来查报错了
前端html里加个try,从div输出报错看看
`index.html`
```html
错误信息终于打出来了
`java.lang.IllegalAccessException: module javafx.web cannot access class com.zzzmh.jfx.controller.Controller (in module com.zzzmh.jfx) because module com.zzzmh.jfx does not open com.zzzmh.jfx.controller to javafx.web`
好家伙!这问题不用百度我就知道了,模块化里没写opens,只能说怪我 `java 11` 没学好,知识水平还永久性的停留在了`java 8`
补上模块化配置
`module-info.java`
```java
module com.zzzmh.jfx {
requires javafx.controls;
requires javafx.web;
requires jdk.jsobject;
// 关键是这行代码
opens com.zzzmh.jfx.controller to javafx.web;
exports com.zzzmh.jfx;
解决完这个问题,终于是跑通了,js可以直接获得java的数据,这样java连数据库就可以获取数据库里的数据了

到这里基本已经大功告成了
顺手再写几个可能以后用得到的简单方法
`Controller.java`
```java
package com.zzzmh.jfx.controller;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class Controller {
* 获取后端数据
public String getData() {
// 这里假装去数据库查询了一套json数据
return "{\"name\":\"张三\",\"age\":9}";
* 新开一个窗口
public void open() {
WebView webView = new WebView();
WebEngine engine = webView.getEngine();
engine.load(Controller.class.getResource(
"/com/zzzmh/jfx/html/new.html").toExternalForm());
Scene scene = new Scene(webView, 400, 280);
Stage stage = new Stage();
stage.setTitle("新开页面");
stage.setScene(scene);
stage.show();
* 彻底退出程序
public void exit(){
Platform.exit();
(配合上文新开窗口这个功能,需在`resources/com.zzzmh.jfx/html/` 下新开一个 `new.html`,内容随意写点,略过)
`index.html`
```html
JavaFX WebView Demo
功能测试
最终执行效果 没有任何问题

**打包二进制**
到这里就大功告成了
结束之前再复习一下上节课研究的打包二进制
复习上节课地址: [2023 IDEA 开发桌面图形界面程序 JavaFX 并导出 Jar 支持多系统](https://zzzmh.cn/post/eee76yv5u9e48dkarmzymnfryohapwm0)
先确保一下maven配置正确
完整maven配置如下
关键是mainClass要对其启动类App.java的main方法
`pom.xml`
```xml
4.0.0
com.zzzmh
JFX-WebView-Demo
1.0-SNAPSHOT
JFX-WebView-Demo
UTF-8
5.9.2
org.openjfx
javafx-controls
17.0.6
org.openjfx
javafx-web
17.0.6
org.apache.maven.plugins
maven-compiler-plugin
3.11.0
11
11
org.openjfx
javafx-maven-plugin
0.0.8
default-cli
com.zzzmh.jfx/com.zzzmh.jfx.App
app
app
app
true
true
true
随后在IDEA的maven中执行 `javafx/javafx:run`
看看能否启动成功
如果成功就可以在maven中执行 `javafx/javafx:jlink` 打包

最终会在根目录 `target` 文件夹下得到打包后的文件
其中 `app.zip` 是所有文件的压缩包 只有 `66.1MB`
这是在包含了所有运行环境 即 `OpenJDK 11` 以及 `javafx.web` 浏览器内核 的情况下,以及算非常小了
通过shell命令就可以执行二进制文件,来启动这个桌面程序
```shell
cd target/app/bin
./app
结果在这里遇到一堆问题
1. 之前一直存在的渲染问题,css在初始化页面的时候不生效
2. 直接执行App的main方法和javafx:run都正常,但jlink打包后的程序用shell执行,前端就找不到window下的controller类
最后做了这几个修改解决
在WebView外面套了一层BorderPane,解决了WebView的渲染问题
在`engine.getLoadWorker().stateProperty().addListener`的外面申明Controller类,在里面传入参数(之前是直接在里面new一个类有问题)
修改后最终解决了上述2个问题
完整代码如下
`App.java`
```java
public class App extends Application {
@Override
public void start(Stage stage) {
BorderPane pane = new BorderPane();
WebView webView = new WebView();
WebEngine engine = webView.getEngine();
Controller controller = new Controller();
// 注入方法
engine.getLoadWorker().stateProperty().addListener(
(ObservableValue ov, Worker.State oldState, Worker.State newState) -> {
if (newState == Worker.State.SUCCEEDED) {
// 获取JS的window对象
JSObject window = (JSObject) engine.executeScript("window");
// 讲controller注入到window对象中
window.setMember("controller", controller);
// 加载页面
engine.load(Controller.class.getResource(
"/com/zzzmh/jfx/html/index.html").toExternalForm());
pane.setCenter(webView);
Scene scene = new Scene(pane, 600, 400);
stage.setTitle("JavaFX WebView Demo");
stage.setScene(scene);
stage.show();
public static void main(String[] args) {
launch();
重新执行了javafx:jlink
用shell cd到bin目录 执行二进制app 成功,功能全部正常
```shell
cd target/app/bin
./app

## END
本文所有源码已发布到开源平台
源码地址:
[https://gitee.com/zzzmhcn/JFX-WebView-Demo](https://gitee.com/zzzmhcn/JFX-WebView-Demo)
[https://github.com/zzzmhcn/JFX-WebView-Demo](https://github.com/zzzmhcn/JFX-WebView-Demo)
最后补充一下jlink我目前尚未解决的一些问题
jlink简单来说就是按需引入的打包,把所有需要的环境都打包成二进制文件
可以直接在windows mac linux执行
(目前测试需要在对应系统打包,比如win打包的只能win执行,linux打包只能linux执行)
最大的优点就是小和方便,正常一个程序连依赖20MB,JDK需要200~300MB
而且JDK需要配置环境变量,且占用系统默认的JDK的位置
jlink打包后才30~60mb,就可以实现不入侵系统直接运行二进制文件
目前一个已知的缺点就是模块化,需要所有代码都是模块化开发,才可以jlink打包
目前已知的例如jedis jdbc都不支持模块化,一旦用了这些依赖就无法打包
我百度了说也有解决办法,但我暂时还没时间深入折腾,以后如果研究出来了再更新