Java 到 JavaScript 编译,结合 AJAX 和 Spring 集成

技术 | 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 确实有一些需要考虑的限制。其中包括
  • 互联网上的下载速度可能较慢。考虑到大多数 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 "http://localhost:8080/echotest/simplerpc";
}

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

字段声明很重要。每个公共的非 transient 字段都将被 SimpleRPCRunnable 序列化。getHttpURL() 指定了 J2S Servlet 的 URL。相同的 URL 可用于任何 J2S 命令,使其成为您应用程序的 J2S 前端控制器。ajaxRun() 方法包含将在服务器端执行的逻辑。在本例中,我们的 ajaxRun() 方法正在访问一个本地(服务器端)Spring bean。请注意 servicesLayer 字段被声明为 transient,这意味着 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 的热衷者,或者需要一个基于成熟 SWT UI 框架构建的以客户端为中心、基于 Web 的用户界面,那么绝对值得仔细看看 J2S。

订阅 Spring 邮件列表

订阅 Spring 邮件列表,保持联系

订阅

保持领先

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

了解更多

获取支持

通过一个简单的订阅,Tanzu Spring 为 OpenJDK™、Spring 和 Apache Tomcat® 提供支持和二进制文件。

了解更多

即将举行的活动

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

查看全部