延迟绑定

延迟绑定是 GWT 编译器的一项功能,它通过在编译时生成多个代码版本来工作,这些代码版本中只有一个需要在运行时启动时由特定客户端加载。每个版本都是针对特定浏览器生成的,以及您的应用程序定义或使用的任何其他轴。例如,如果您要使用 GWT 的国际化模块 国际化您的应用程序,GWT 编译器将为每个浏览器环境生成您应用程序的不同版本,例如“Firefox 英文版”、“Firefox 法文版”、“Internet Explorer 英文版”等等……因此,部署的 JavaScript 代码是紧凑的,并且比手动编写的 JavaScript 代码下载速度更快,因为它只包含特定浏览器环境所需的代码和资源。

  1. 延迟绑定的优势
  2. 定义延迟绑定规则
  3. 模块 XML 文件中的指令
  4. 使用替换进行延迟绑定
  5. 使用替换的示例类层次结构
  6. 使用生成器进行延迟绑定
  7. 模块 XML 中的生成器配置
  8. 生成器实现

延迟绑定的优势

延迟绑定是 GWT 编译器使用的一种技术,用于根据一组参数创建和选择类的特定实现。本质上,延迟绑定是 GWT 对 Java 反射的答案。它允许 GWT 开发人员生成应用程序的多个变体,这些变体针对每个浏览器环境进行定制,并且只下载和执行其中一个变体。

延迟绑定有以下几个优势

  • 通过只包含运行特定浏览器/区域设置实例所需的代码来减少客户端需要下载的生成的 JavaScript 代码的大小(由 国际化模块 使用)
  • 通过自动生成代码来实现接口或创建代理类来节省开发时间(由 GWT RPC 模块 使用)
  • 由于实现是在编译时预绑定的,因此没有像动态绑定或使用虚拟函数那样在运行时查找数据结构中实现的开销。

工具包的某些部分隐式使用延迟绑定,也就是说,它们将该技术作为其实现的一部分使用,但对 API 用户来说是不可见的。例如,许多 小部件和 面板 以及 DOM 类使用此技术来实现特定于浏览器的逻辑。其他 GWT 功能需要 API 用户通过设计遵循特定规则的类并使用 GWT.create(Class) 实例化这些类的实例来显式调用延迟绑定,包括 GWT RPCI18N

作为 GWT 的用户,您可能永远不需要创建使用延迟绑定的新接口。如果您按照指南中创建国际化应用程序或 GWT RPC 调用的说明进行操作,您将使用延迟绑定,但您不必实际编写任何依赖于浏览器或区域设置的代码。

延迟绑定部分的其余部分介绍了如何使用延迟绑定创建新的规则和类。如果您是该工具包的新手,或者只打算使用预打包的小部件,您可能希望跳过下一个主题。如果您有兴趣从头开始编程全新的 widgets 或其他需要跨浏览器依赖代码的功能,那么接下来的部分应该会让您感兴趣。

定义延迟绑定规则

有两种方法可以根据延迟绑定替换类型

  • 替换:根据一组可配置的规则,将一种类型替换为另一种类型。
  • 代码生成:将一种类型替换为在编译时调用代码生成器的结果。

模块 XML 文件中的指令

延迟绑定机制是完全可配置的,不需要编辑 GWT 分发的源代码。延迟绑定是通过 模块 XML 文件 中的 <replace-with><generate-with> 元素进行配置的。延迟绑定规则通过 <inherits> 元素被拉入模块构建中。

例如,以下配置为 PopupPanel 小部件调用延迟绑定

PopupPanel 模块 XML 文件中,碰巧有一些为延迟绑定定义的规则。在这种情况下,我们正在使用替换规则。

使用替换进行延迟绑定

第一种延迟绑定类型使用替换。替换意味着根据编译时的确定结果,用另一个类覆盖一个 Java 类的实现。例如,此技术用于对某些小部件的实现进行条件化,例如 PopupPanel。在上一节中描述了延迟绑定规则,显示了对 PopupPanel 类使用 <inherits> 的情况。实际的替换规则在 Popup.gwt.xml 中指定,如下所示

<module>

  <!--  ... other configuration omitted ... -->

  <!-- Fall through to this rule is the browser isn't IE or Mozilla -->
  <replace-with class="com.google.gwt.user.client.ui.impl.PopupImpl">
    <when-type-is class="com.google.gwt.user.client.ui.impl.PopupImpl"/>
  </replace-with>

  <!-- Mozilla needs a different implementation due to issue #410 -->
  <replace-with class="com.google.gwt.user.client.ui.impl.PopupImplMozilla">
    <when-type-is class="com.google.gwt.user.client.ui.impl.PopupImpl" />
    <any>
      <when-property-is name="user.agent" value="gecko"/>
      <when-property-is name="user.agent" value="gecko1_8" />
    </any>
  </replace-with>

  <!-- IE has a completely different popup implementation -->
  <replace-with class="com.google.gwt.user.client.ui.impl.PopupImplIE6">
    <when-type-is class="com.google.gwt.user.client.ui.impl.PopupImpl"/>
    <when-property-is name="user.agent" value="ie6" />
  </replace-with>
</module>

这些指令告诉 GWT 编译器根据 user.agent 属性用不同的类实现替换 PopupImpl 类代码。Popup.gwt.xml 文件指定了 PopupImpl 类的默认实现,Mozilla 浏览器的覆盖(PopupImplMozilla 替换 PopupImpl),以及 Internet Explorer 版本 6 的覆盖(PopupImplIE6 替换 PopupImpl)。请注意,PopupImpl 类或其派生类不能直接实例化。相反,PopupPanel 类被使用,并且在幕后使用 GWT.create(Class) 技术来指示编译器使用延迟绑定。

使用替换的示例类层次结构

为了了解在设计小部件时如何使用此技术,我们将进一步研究 PopupPanel 小部件的情况。PopupPanel 类实现了对用户可见的 API,并且包含对所有浏览器通用的逻辑。它还使用 GWT.create(Class) 实例化适当的特定于实现的逻辑,如下所示

private static final PopupImpl impl = GWT.create(PopupImpl.class);

PopupImplMozilla 和 PopupImplIE6 这两个类扩展了 PopupImpl 类,并覆盖了某些 PopupImpl 的方法以实现特定于浏览器的行为。

然后,当 PopupPanel 类需要切换到一些依赖于浏览器的代码时,它会访问 PopupImpl 类中的一个成员函数

public void setVisible(boolean visible) {
    // ... common code for all implementations of PopupPanel ...

    // If the PopupImpl creates an iframe shim, it's also necessary to hide it
    // as well.
    impl.setVisible(getElement(), visible);
  }

PopupImpl.setVisible() 的默认实现是空的,但 PopupImplIE6 实现了一些特殊的逻辑,作为 JSNI 方法

public native void setVisible(Element popup, boolean visible) /*-{
    if (popup.__frame) {
      popup.__frame.style.visibility = visible ? 'visible' : 'hidden';
    }
  }-*/;{

在 GWT 编译器运行之后,它会修剪掉任何未使用的代码。如果您的应用程序引用了 PopupPanel 类,编译器将为每个浏览器创建一个单独的 JavaScript 输出文件,每个文件只包含一个实现:PopupImplPopupImplIE6PopupImplMozilla。这意味着每个浏览器只下载它需要的实现,从而减小了输出 JavaScript 代码的大小,并最大程度地减少了从服务器下载应用程序所需的时间。

使用生成器进行延迟绑定

延迟绑定的第二种技术是使用生成器。生成器是在编译时由 GWT 编译器调用以生成类的 Java 实现的类。当编译为生产模式时,生成的实现将直接转换为客户端将根据其浏览器环境下载的应用程序版本的 JavaScript 代码之一。

以下是关于如何为 `RemoteService` 类(用于 GWT-RPC)在 模块 XML 文件 层次结构中指定延迟绑定生成器的示例。

模块 XML 中的生成器配置

XML 元素 `<generate-with>` 告诉编译器使用 `Generator` 类。以下是 `RemoteService.gwt.xml` 文件中与延迟绑定相关的部分内容。

<module>

 <!--  ... other configuration omitted ... -->

 <!-- Default warning for non-static, final fields enabled -->
 <set-property name="gwt.suppressNonStaticFinalFieldWarnings" value="false" />

 <generate-with class="com.google.gwt.user.rebind.rpc.ServiceInterfaceProxyGenerator">
   <when-type-assignable class="com.google.gwt.user.client.rpc.RemoteService" />
 </generate-with>
</module>

这些指令指示 GWT 编译器调用 `Generator` 子类(`ServiceInterfaceProxyGenerator`)中的方法,以便在编译时遇到延迟绑定机制 GWT.create() 时生成特殊代码。在这种情况下,如果 GWT.create() 调用引用 `RemoteService` 或其子类的实例,则会调用 `ServiceInterfaceProxyGenerator` 的 `generate()` 方法。

生成器实现

定义 `Generator` 类的子类类似于为 GWT 编译器定义插件。`Generator` 在 Java 到 JavaScript 转换发生之前被调用以生成 Java 类定义。实现包含一个方法,该方法必须将 Java 代码输出到文件并以字符串形式返回生成的类的名称。

以下代码显示了负责 `RemoteService` 接口延迟绑定的 `Generator`。

/**
 * Generator for producing the asynchronous version of a
 * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} interface.
 */
public class ServiceInterfaceProxyGenerator extends Generator {

  /**
   * Generate a default constructible subclass of the requested type. The
   * generator throws <code>UnableToCompleteException</code> if for any reason
   * it cannot provide a substitute class
   *
   * @return the name of a subclass to substitute for the requested class, or
   *         return <code>null</code> to cause the requested type itself to be
   *         used
   *
   */
  public String generate(TreeLogger logger, GeneratorContext ctx,
      String requestedClass) throws UnableToCompleteException {

    TypeOracle typeOracle = ctx.getTypeOracle();
    assert (typeOracle != null);

    JClassType remoteService = typeOracle.findType(requestedClass);
    if (remoteService == null) {
      logger.log(TreeLogger.ERROR, "Unable to find metadata for type '"
          + requestedClass + "'", null);
      throw new UnableToCompleteException();
    }

    if (remoteService.isInterface() == null) {
      logger.log(TreeLogger.ERROR, remoteService.getQualifiedSourceName()
          + " is not an interface", null);
      throw new UnableToCompleteException();
    }

    ProxyCreator proxyCreator = new ProxyCreator(remoteService);

    TreeLogger proxyLogger = logger.branch(TreeLogger.DEBUG,
        "Generating client proxy for remote service interface '"
            + remoteService.getQualifiedSourceName() + "'", null);

    return proxyCreator.create(proxyLogger, ctx);
  }
}

`typeOracle` 是一个包含已解析的 Java 代码信息的對象,生成器可能需要查阅这些信息。在这种情况下,`generate()` 方法检查其参数并将大部分工作传递给另一个类(`ProxyCreator`)。