1. 概述

TeaVM 是一个强大的工具,能将 Java 字节码翻译成 JavaScript,让 Java 应用直接在浏览器中运行。这意味着我们可以保持 Java 代码库的同时,轻松适配 Web 环境。

本文将探讨如何利用 TeaVM 的能力,实现 Java 与 JavaScript 的交互,包括操作 DOM 和从 JavaScript 调用 Java 方法。

2. TeaVM 的主要用途

当项目包含复杂的 Java 逻辑时,重写 Web 版本显然不现实。TeaVM 通过高效编译 Java 代码为 JavaScript,帮我们避免重复劳动,同时优化最终脚本的体积和性能。

⚠️ 注意:Java 代码必须符合 Java 类库 (JCL) 模拟 的限制。比如 Swing 和 JavaFX 就不支持。

2.1 单页应用 (SPA)

TeaVM 非常适合从零开始构建单页应用,几乎完全用 Java 和 CSS 实现,大幅减少 JavaScript 和 HTML 的编写量。这得益于它的 Web 特性:

  • 直接从 JavaScript 调用 Java 方法(后续示例会演示)
  • 使用 @JSBody 注解创建 JavaScript 实现的原生 Java 方法
  • 通过 JSO 库 用 Java 操作 Web 元素

但 TeaVM 不能完全替代 JavaScript 或 HTML,特别是布局和浏览器特定逻辑。另外 CSS 需要手动编写或通过框架管理。

2.2 现有网站集成

TeaVM 也适合为 WordPress 或 Joomla 等传统 CMS 开发的网站添加 Java 功能。例如,我们可以开发 CMS 插件,通过 REST API 将功能暴露给 TeaVM,然后在页面中引入 TeaVM 编译的 JavaScript。

这种脚本能利用 REST API 实现 JSON 与 Java 对象的互转,执行 Java 业务逻辑,还能修改 DOM 创建或集成用户界面。

当客户端业务逻辑极其复杂时,这种方案特别有价值——毕竟用 Java 比用 JavaScript 更顺手、更熟悉。

2.3 其他场景

TeaVM 的 WebAssembly 支持正在积极开发中,新版本将允许以极小改动将 JavaScript 应用移植到 WebAssembly。不过目前仍是实验阶段。

TeaVM 还能将 Java 转译为 C,可作为 GraalVM 原生镜像的超轻量子集使用。虽然此功能稳定可用,但已超出 TeaVM 的主要用途,本文不展开。

3. Maven 配置与 TeaVM 设置

当前 TeaVM 0.10.x 版本支持 JDK 21 的字节码,但运行编译器至少需要 Java 11。未来版本要求可能变化。

我们将用 Maven,并通过 Java 类编程配置 TeaVM,保持 pom.xml 简洁。这种方式能通过 Maven 的 -Dexec.args 选项动态调整配置,方便从同一代码库生成不同用途的 JavaScript 输出。

若使用其他构建工具,可参考官方 TeaVM 入门指南

3.1 pom.xml 配置

Maven 仓库 查阅最新版本后添加依赖:

<dependency>
    <groupId>org.teavm</groupId>
    <artifactId>teavm-core</artifactId>
    <version>0.10.2</version>
</dependency>
<dependency>
    <groupId>org.teavm</groupId>
    <artifactId>teavm-classlib</artifactId>
    <version>0.10.2</version>
</dependency>
<dependency>
    <groupId>org.teavm</groupId>
    <artifactId>teavm-tooling</artifactId>
    <version>0.10.2</version>
</dependency>

这三个依赖会自动引入其他传递依赖:

  • teavm-core 包含 teavm-interop、字节码操作库(ASM)、HPPC 和 Rhino 引擎
  • teavm-classlib 包含 teavm-jsocommons-iojzlibjoda-time
  • teavm-tooling 包含重定位版的 commons-io

这些依赖提供了核心功能,无需手动添加

3.2 TeaVMRunner.java 配置类

通过 Maven 的 -Dexec.mainClass 指定配置类:

public class TeaVMRunner {
    public static void main(String[] args) throws Exception {
        TeaVMTool tool = new TeaVMTool();
        tool.setTargetDirectory(new File("target/teavm"));
        tool.setTargetFileName("calculator.js");
        tool.setMainClass("com.baeldung.teavm.Calculator");
        tool.setTargetType(TeaVMTargetType.JAVASCRIPT);
        tool.setOptimizationLevel(TeaVMOptimizationLevel.ADVANCED);
        tool.setDebugInformationGenerated(false);
        tool.setIncremental(false);
        tool.setObfuscated(true);
        tool.generate();
    }
}

配置项详解:

  • setTargetDirectory(...) → 输出目录
  • setTargetFileName(...) → 生成的 JavaScript 文件名
  • ***setMainClass(...) → 应用主类的全限定名**
  • setTargetType(...) → 目标类型(JavaScript/WebAssembly/C)
  • setOptimizationLevel(...)ADVANCED 级别生成最小体积文件(比 FULL 更小)
  • setDebugInformationGenerated(...) → 仅用于生成 Eclipse 插件调试信息
  • setIncremental(...) → 增量编译更快但优化少(生产环境不推荐)
  • setObfuscated(...) → 启用后体积减少 2-3 倍(推荐开启)

其他选项见官方工具文档(如生成 source maps)。

关键点setMainClass(...) 会创建全局 main() 函数,执行翻译后的 Java main(String[]) 方法。后续有示例演示。

3.3 HTML 页面

这是引用 TeaVM 生成文件的极简示例(省略了移动端优化和索引相关 meta 标签):

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>TeaVM 示例</title>
    <script type="text/javascript" src="calculator.js"></script>
    <script type="text/javascript">
        document.addEventListener('DOMContentLoaded', function() {
            // 此处调用 JS 函数
        });
    </script>
</head>
<body>
<h1>TeaVM 示例</h1>
<div id="calculator-container"></div>
</body>
</html>

calculator.js 是之前在 setTargetFileName(...) 中指定的文件名。稍后会说明如何替换 "此处调用 JS 函数" 的注释<div id="calculator-container"></div> 是占位符,用于创建计算器示例。

4. 计算器示例

我们用一个简单的加法运算演示。

4.1 从 JavaScript 调用 Java 方法

这是之前在 setMainClass(...) 中指定的类:

public class Calculator {

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

    @JSExport
    public static int sum(int a, int b) {
        return a + b;
    }
}

@JSExport 注解使 Java 方法可被 JavaScript 调用。编译后,在 HTML 中调用 sum(...)

[...]
document.addEventListener('DOMContentLoaded', function() {
    // 此处调用 JS 函数
    let result = sum(51, 72);
    console.log("计算结果: " + result);
[...]

浏览器控制台输出:

计算结果: 123

结果符合预期。

4.2 基于 Java 的 DOM 操作

现在实现 main(...) 方法。注意 document.getElementById("calculator-container") 会选中 HTML 中的占位符 <div>

public static void main(String[] args) {
    HTMLDocument document = HTMLDocument.current();
    HTMLElement container = document.getElementById("calculator-container");

    // 创建输入框
    HTMLInputElement input1 = (HTMLInputElement) document.createElement("input");
    input1.setType("number");
    container.appendChild(input1);

    HTMLInputElement input2 = (HTMLInputElement) document.createElement("input");
    input2.setType("number");
    container.appendChild(input2);

    // 创建按钮
    HTMLButtonElement button = (HTMLButtonElement) document.createElement("button");
    button.appendChild(document.createTextNode("计算求和"));
    container.appendChild(button);

    // 创建结果显示区
    HTMLElement resultDiv = document.createElement("div");
    container.appendChild(resultDiv);

    // 添加按钮点击事件
    button.addEventListener("click", (evt) -> {
        try {
            long num1 = Long.parseLong(input1.getValue());
            long num2 = Long.parseLong(input2.getValue());
            long sum = num1 + num2;
            resultDiv.setTextContent("结果: " + sum);
        } catch (NumberFormatException e) {
            resultDiv.setTextContent("请输入有效的整数");
        }
    });
}

代码逻辑清晰:用 Java 动态创建输入框、按钮和结果区。按钮监听点击事件,获取输入值计算求和并显示结果,输入无效时提示错误。

别忘了调用 main()

document.addEventListener('DOMContentLoaded', function() {
    // 此处调用 JS 函数
    main();
});

运行效果如下:

TeaVM 计算器示例

从这种简单示例起步,结合 CSS 就能自由修改 DOM 创建任意布局。

5. 总结

本文探讨了 TeaVM 如何将 Java 字节码转换为 JavaScript,使 Java 应用能直接在浏览器中运行。我们覆盖了核心功能:从 JavaScript 调用 Java 方法、基于 Java 的 DOM 操作,以及无需大量 JavaScript 代码实现简单 Web 应用。通过计算器实例,展示了使用 TeaVM 桥接 Java 与 Web 开发的便利性。

✅ 完整源代码可在 GitHub 获取。


原始标题:Introduction to TeaVM | Baeldung