使用 AJAX 和 Spring 集成进行 Java 到 JavaScript 编译

工程 | Ben Alex | 2007 年 1 月 22 日 | ...

一段时间以来,我一直对以客户端为中心的基于 Web 的用户界面感兴趣。这些第四代框架的特点是其基于组件、事件驱动的编程模型,并专注于完全驻留在客户端的表示逻辑。以这种方式针对 Web 浏览器通常需要使用 JavaScriptFlash,这本身就带来了一些独特的挑战。

如果我们能够使用 Java 编程,并自动生成 JavaScript 或 Flash 运行时模块,那么许多这些挑战就可以得到解决。目前,有两个知名的产品可以实现这一点,分别是 Google Web Toolkit (GWT) 和 Open Laszlo。两者都根据 OSI 批准的许可证 提供,并拥有活跃的社区,但各自都有其独特的复杂性。一个需要考虑的方面是,它们在多大程度上实现了提供一个面向 Web 浏览器部署的透明 Java 开发环境的目标。这个考虑有几个方面,包括 IDE 支持、调试集成、反射能力、运行时小部件绑定等等。所有这些都是使用传统的 Java 技术(如 SwingStandard Widget Toolkit (SWT))开发富客户端时的常规考虑因素。

这篇博文的目的不是评测 GWT 或 Open Laszlo。相反,我想探讨一个名为 Java2Script Pacemaker (J2S) 的开源 Java 转 JavaScript 编译器,并介绍一个初步的 Spring 集成。这个有趣的项目不太为人所知,但它以一种令人鼓舞的方式解决了透明的 Java 转 JavaScript 开发问题。J2S 附带一个增量编译器,几乎完整的 `java.lang`、`java.io` 和 `java.util` 包的 JavaScript 版本,一个 Junit 的 JavaScript 版本,一个 Eclipse SWT 的 JavaScript 实现,以及一个 AJAX 库。更重要的是,J2S 可以将任何现有的 Java 代码转换为 JavaScript,前提是源代码和依赖项也能够以类似的方式转换为 JavaScript。

从技术角度来看,J2S 目前在几个大的方面与 GWT 不同。首先是其编译器技术,它构建在 Eclipse 的 抽象语法树 (AST) 之上,因此需要 Eclipse。然而,Eclipse JDT Core 支持无头模式,因此从 Ant 插件或 Maven mojo 执行 J2S 编译不会很难。第二个区别是 J2S 提供了全面的运行时反射和小部件绑定功能。GWT 倾向于在编译时进行 JavaScript 优化,牺牲了这些运行时服务。另一方面,J2S 认为 摩尔定律、改进的浏览器 JavaScript 解释器以及 JNI 类似的 JavaScript 优化共同提供了足够的性能空间,同时仍然享受更完整的 JRE 仿真和其他运行时服务。

也许最大的技术差异与用户界面方法有关。GWT 提供了自己的类似 Swing 的 API,该 API 是为 Web 浏览器集成而设计的。另一方面,J2S 旨在提供 SWT 的实现。J2S 的方法有很多显而易见的优势

  • 您的应用程序可以针对富客户端(在 JVM 中)和 Web UI(在 Web 浏览器中),只需少量甚至无需重写代码;
  • 开发和调试 J2S 应用程序与开发和调试普通的 SWT 应用程序基本相同,这大大降低了新开发人员的学习曲线;
  • 组织有机会吸引具有 SWT 技能的人员;
  • SWT 有大量的 文献社区 资源 可用;
  • SWT 是一个稳定且经过生产验证的 API;以及
  • 可以使用 开源商业 工具来帮助您构建 SWT 应用程序。
不过,J2S 确实存在一些需要考虑的局限性。这些包括
  • 通过 Internet 下载速度可能很慢。考虑到大多数 JEE 应用程序都用于内网部署,我不确定这是否是一个主要障碍。此外,JavaScript 压缩和客户端缓存可以最大限度地减少这些延迟。
  • 执行速度可能很慢,尽管代码下载后我并未觉得太糟糕。有各种优化可用以提高速度。此外,还有为有能力的用户提供 JVM 托管的 SWT 版本用户界面的选项,由于共享的 SWT 代码库,这应不会增加太多额外的开发成本。
  • JFace 目前不受支持,并且完整的 JRE 未被仿真。一个值得注意的例外是 `java.io.File`。如果您的应用程序依赖于此类,它将无法原生编译为 JavaScript。相反,您需要使用 J2S 的 `@j2sNative` 功能将相关类编译为 JavaScript。我还遇到了一些 SWT 实现的微小问题,但都不算太严重。
  • J2S 的 社区相对较小,该框架迄今为止尚未得到广泛应用。不过,每个开源项目都从小处开始,需要有机会成长。
  • Eclipse 以外 IDE 的用户将无法使用当前形式的 J2S。如前所述,AST 编译模型允许 Eclipse JDT 支持无头模式,因此这并不是一个主要问题。
Spring 社区也会理所当然地想知道,“它能与 Spring 一起工作吗?”。答案实际上取决于您想达到什么目的。如果您打算构建标准的 SWT 或 Swing 应用程序,通常会在用户界面层使用这些技术来访问远程服务层。因此,您主要的 Spring 集成问题在于是否存在合适的远程机制。Spring 为 Java 到 Java 的远程调用提供了广泛的成熟 基础设施,大多数项目选择同步的 HttpInvokerRMISOAP

通过生成基于 JavaScript 的客户端,J2S 显然需要某种形式的 Java 到 JavaScript 远程调用。Java 到 JavaScript 的远程调用实现采用异步方法是很常见的,这意味着在远程调用后执行会立即继续,并且在接收到调用结果后有一个单独的回调来处理。Java 到 JavaScript 远程调用的两种主要方法是 DWRJSON-RPC,尽管 GWT 和 J2S 都提供了自己独立的远程调用方法。GWT 和 J2S 的方法都不能开箱即用地提供 Spring 集成,尽管 Spring 的灵活架构使得这样做相当容易(正如我下面将在 J2S 的情况下向您展示的那样)。

在我们查看 Spring 实现之前,让我们回顾一下 J2S AJAX 远程调用协议是如何工作的。J2S 为每个潜在的远程调用采用一种准 命令模式。`SimpleRPCRunnable` 超类提供了 JavaScript 到 Java 和 Java 到 JavaScript 的序列化,子类指示远程 URL、要序列化的字段以及要在远程执行的逻辑。


public class LZ77JSSimpleRPCRunnable extends SimpleRPCRunnable {

private transient SomeServicesLayer servicesLayer; // setter omitted
public String jsContent;
public String result;

public String getHttpURL() {
return "https://:8080/echotest/simplerpc";
}

public void ajaxRun() {
result = servicesLayer.computeTheAnswer(jsContent);
jsContent = null;
}
}

字段声明很重要。每个公共的非瞬态字段都会被 `SimpleRPCRunnable` 序列化。`getHttpURL()` 指定了 J2S servlet 的 URL。任何 J2S 命令都可以使用相同的 URL,使其成为您应用程序的 J2S 前端控制器。`ajaxRun()` 方法包含将在服务器端执行的逻辑。在本例中,我们的 `ajaxRun()` 方法正在访问本地(服务器端)的 Spring bean。注意 `servicesLayer` 字段声明为瞬态,这意味着 `SimpleRPCRunnable` 不会序列化它。取而代之的是,Spring IoC 容器会将 `SomeServicesLayer` 实例依赖注入到我们的服务器端命令对象中。因此,`servicesLayer` 在 J2S 客户端端始终为 null。为了让客户端异步调用该命令,他们会使用类似以下的代码:


SimpleRPCSWTRequest.swtRequest(new LZ77JSSimpleRPCRunnable() {

public void ajaxIn() {
jsContent = sourceText.getText();
}

public void ajaxOut() {
resultText.setText(result);
}
});

如所示,`ajaxIn()` 方法用于在客户端上将公共字段设置为可接受的值。`ajaxOut()` 方法是异步回调处理程序,这意味着一旦命令对象从服务器返回并反序列化后,就会执行它。在本例中,命令正在更新一个 UI 小部件。下面的屏幕截图显示了在 JVM 托管的 SWT 应用程序中执行该命令的结果。

Figure 1
下一个屏幕截图显示了在 Firefox 托管的 SWT 应用程序中执行同一命令的结果。在这些运行时目标之间,无需更改任何代码或远程配置,这说明了 J2S 方法的灵活性和吸引力。
Figure 2
`SimpleRPCSWTRequest` 还提供了两个静态方法来声明调用是否应该实际通过网络发生。`SimpleRPCSWTRequest.switchToLocalJavaThreadMode()` 将导致 `ajaxRun()` 方法在本地调用,这在运行 JVM 托管的 SWT 应用程序时可能很合适。要使调用跨网络序列化(并因此 `ajaxRun()` 在服务器端执行),只需调用 `SimpleRPCSWTRequest.switchToAJAXMode()`。此模式与浏览器和 JVM 目标平台都兼容,因此使用 J2S 构建多目标用户界面不需要为 JVM 目标使用额外的远程协议(例如 HttpInvoker 或 RMI)。

在服务器端,我们没有使用普通的 J2S `SimpleRPCHttpServlet`。相反,我们正在使用一个名为 `SpringRpcHttpServlet` 的新类(该类与本文中引用的所有其他代码一起,作为 ZIP 附件 提供)。`SpringRpcHttpServlet` 的操作与普通的 `SimpleRPCHttpServlet` 相同,除了它从 Spring 应用程序上下文中获取服务器端命令对象。代码文档齐全,如果您有兴趣详细了解它是如何工作的,请查看 ZIP 附件。它基本上允许您在 Spring 应用程序上下文文件中定义命令及其依赖项。

如果您的应用程序需要其他命令,只需创建一个 `SimpleRPCRunnable` 子类,然后将其添加到您的应用程序上下文中即可。关注我工作的人可能会对 ROO 感兴趣,他们会很高兴听到我打算提供 J2S 远程集成,让您无需编写命令对象或通过 `SimpleRPCSWTRequest` 调用。

总之,J2S 为需要 JavaScript 编译或 SWT 的 Web 浏览器实现的项目提供了有吸引力的优势。它还可以成功地与 Spring 后端互操作。J2S 刻意选择利用现有的成熟技术,如 AST 和 SWT,使其成为重用现有代码和开发人员技能的一个很好的例子,从而降低了采用门槛和重大 API 更改的可能性。如果您认为自己是早期采用者、SWT 的拥护者,或者需要一个以客户端为中心、基于 Web 的用户界面,并且构建在成熟的 SWT UI 框架之上,那么 J2S 绝对值得您进一步关注。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有