Chapter 22. Seam和Google的Web工具包(GWT)

对于那些宁愿使用Google Web Toolkit(以下简称:GWT)去开发动态的AJAX的应用程序的人们来说,Seam提供了一个允许GWT widget直接与Seam组件交互的整合层。

为了使用GWT,我们假设你已经很熟悉GWT工具 - 更多信息可以在 http://code.google.com/webtoolkit/ 中找到。 本章就不去解释GWT如何工作以及如何使用了。

22.1. 配置

在Seam应用程序中使用GWT并不需要特别的配置,但是Seam资源的servlet是必须安装的。更多详情请看 Chapter 25, Seam配置和Seam应用程序打包

22.2. 准备你的组件

准备一个要通过GWT进行调用的Seam组件的第一步,是给你想要调用的方法创建同步和异步的服务接口。这两个接口都应该扩展GWT接口 com.google.gwt.user.client.rpc.RemoteService

  public interface MyService extends RemoteService
  {
    public String askIt(String question);
  }

异步接口应该完全相同,除此之外,它还要给它声明的每一个方法包含一个额外的 AsyncCallback 参数:

  public interface MyServiceAsync extends RemoteService
  {
    public void askIt(String question, AsyncCallback callback);
  }

在这个范例 MyServiceAsync 中,异步接口将通过GWT实现,并且永远不应该直接实现。

下一步,是创建一个实现同步接口的Seam组件:

  @Name("org.jboss.seam.example.remoting.gwt.client.MyService")
  public class ServiceImpl implements MyService
  {
    @WebRemote
    public String askIt(String question)
    {
      if (!validate(question))
      {
        throw new IllegalStateException("Hey, this shouldn't happen, I checked on the client, " +
               "but its always good to double check.");
      }
      return "42. Its the real question that you seek now.";
    }

    public boolean validate(String q)
    {
      ValidationUtility util = new ValidationUtility();
      return util.isValid(q);
    }
  }

应该做成能通过GWT访问的那些方法,需要通过标识 @WebRemote 进行注解,这是所有能够进行Web远程访问的方法都要求的。

22.3. 将GWT小组件接到Seam组件

下一步,是编写一个将异步接口返回给组件的方法。这个方法可以放在小组件类里面,将被小组件用来获得一个对异步客户端存根(stub)的引用:

   private MyServiceAsync getService()
   {
      String endpointURL = GWT.getModuleBaseURL() + "seam/resource/gwt";

      MyServiceAsync svc = (MyServiceAsync) GWT.create(MyService.class);
      ((ServiceDefTarget) svc).setServiceEntryPoint(endpointURL);
      return svc;
   }

最后一步就是编写小组件代码,它在客户端存根上调用方法。以下代码创建一个简单的用户接口,包含label(标签)、text input(文本输入框)和一个button(按钮):

public class AskQuestionWidget extends Composite
{
   private AbsolutePanel panel = new AbsolutePanel();

   public AskQuestionWidget()
   {
      Label lbl = new Label("OK, what do you want to know?");
      panel.add(lbl);
      final TextBox box = new TextBox();
      box.setText("What is the meaning of life?");
      panel.add(box);
      Button ok = new Button("Ask");
      ok.addClickListener(new ClickListener()
      {
         public void onClick(Widget w)
         {
            ValidationUtility valid = new ValidationUtility();
            if (!valid.isValid(box.getText()))
            {
               Window.alert("A question has to end with a '?'");
            }
            else
            {
               askServer(box.getText());
            }
         }
      });
      panel.add(ok);

      initWidget(panel);
   }

   private void askServer(String text)
   {
      getService().askIt(text, new AsyncCallback()
      {
         public void onFailure(Throwable t)
         {
            Window.alert(t.getMessage());
         }

         public void onSuccess(Object data)
         {
            Window.alert((String) data);
         }
      });
   }

   ...
    

当点击按钮时,它就调用 askServer() 方法来传递输入框的内容(在这个例子中,还执行了验证,以确保输入的是有效的问题)。这个 askServer() 方法获得一个对异步客户端存根的引用(由 getService() 方法返回),并调用 askIt() 方法。这个结果(或者调用失败时的错误信息)显示在以一个警告窗口中。

这个例子的完整代码可以在Seam发行包的 examples/remoting/gwt 中找到。

22.4. GWT Ant Targets

对于GWT应用程序的发布(部署)来说,有一个编译成JavaScript的步骤(它压缩和混淆了代码)。 有一个Ant实用程序可以用来取代GWT提供的命令行或者GUI实用程序。 为了使用这个功能,你不仅在Ant classpath中要有Ant的任务jar包,还要下载GWT(无论怎样,你在本机模式下时也会需要它)。

接下来在你的Ant文件中(在你的Ant文件顶头附近)

  <taskdef uri="antlib:de.samaflost.gwttasks"
                resource="de/samaflost/gwttasks/antlib.xml"
                classpath="./lib/gwttasks.jar"/>

  <property file="build.properties"/>

创建一个 build.properties 文件,它包括以下内容:

gwt.home=/gwt_home_dir

这当然应该指向GWT的安装路径。然后用它创建一个Target:

  <!-- the following are are handy utilities for doing GWT development.
      To use GWT, you will of course need to download GWT seperately -->
  <target name="gwt-compile">
      <!-- in this case, we are "re homing" the gwt generated stuff, so in this case
      we can only have one GWT module - we are doing this deliberately to keep the URL short -->
      <delete>
          <fileset dir="view"/>
      </delete>
      <gwt:compile outDir="build/gwt"
          gwtHome="${gwt.home}"
          classBase="${gwt.module.name}"
          sourceclasspath="src"/>
      <copy todir="view">
          <fileset dir="build/gwt/${gwt.module.name}"/>
      </copy>
  </target>

This target when called will compile the GWT application, and copy it to the specified directory (which would be in the webapp part of your war - remember GWT generates HTML and Javascript artifacts). You never edit the resulting code that gwt-compile generates - you always edit in the GWT source directory. 当这个Target被调用时,将编译GWT应用程序,并将它复制到指定的目录中(它会在你war的 webapp 部分中 —— 记住GWT生成HTML和Javascript工件)。 你永远不用编辑 gwt-compile 生成的结果代码 —— 你始终在GWT源路径中进行编辑。

记住GWT有一个本机模式浏览器 —— 你在用GWT开发时使用的应该就是它。 如果你没有使用该浏览器,而只是每次对它进行编译,那你就没有充分利用这个工具包(事实上,如果你无法或者没有使用本机模式浏览器,我只能说你根本不应该使用GWT —— 它非常有用!)