老早xc就在我耳边絮絮叨叨gwt的好处呢,可是由于受google maps在我浏览器里总无法正常显示的影响(莫非是人品问题?), 我差点就与它擦肩而过. 还是因为Cain 某天在群里述说自己因为没有删除gwt-user.jar中javax部分所受的痛苦折磨,才让我决心一试gwt, 呵呵, 老大都出手了,小弟怎能落后呢?
    关于gwt的详细内容我就不多介绍了,具体可以参见我blog上转载的两篇文章Google Web Toolkit 入门和gwt study .这里就主要结合我自己的试用经历讲下吧.
    ajax现在的应用也很广泛了,其中可能用的最多,也几乎会在每本介绍ajax的书上出现的案例,大概就是关于用户注册时对用户名的检验.因此我也选中用gwt完成类似的功能.考虑到demo的简易性,具体的功能缩减如下:
      在输入框中输入用户名后,将鼠标点离输入框, 程序将调用后台servlet自动检验输入信息.如果输入内容为空,则提示错误信息;如果输入信息为dyerac,则提示该用户已注册(本来应该是先检验数据库看用户名是否已经存在,为了简便我省去了这一步,直接用dyerac替代);如果都不是,则显示出欢迎信息.
     同样,你也可以直接点击"test ajax"按键手工进行检验,检验原则和前面相同
        
      
     好咯,下面就让我们一步步来吧.
    1.下载gwt
   从 http://code.google.com/webtoolkit/  下载GWT的最新版本,将下载的压缩文件解压缩到D:/GWT目录下。本书中的后续内容中将使用%GWT_HOME%变量来引用这个目录。点开目录,会发现目录中含有三个cmd文件和两个jar文件:
               
     分别将他们加入classpath和path中
 
    2.创建工程:
    创建目录%GWT_HOME%/gwt-workspace/gwtTrial, 通过命令行进入到该目录下,输入命令projectCreator       -eclipse gwtTrial  创建eclipse同名工程. 然后用eclipse将该工程导入. 
    在eclipse下给项目添加两个包: com.dyerac.client,  com.dyerac.server.注意,gwt会把你的java代码翻译成js代码,由于gwt的强制规定,所以需要翻译的java代码都必须放到以client结尾的包中,同时,为了程序的易读性,gwt也建议把servlet放在server结尾的包中.
    3.代码开发
    gwt最方便的地方之一在于,你完全不用开发js代码, gwt仿照awt开发了自己的一套组件库,你可以通过这套组件库像开发swing一样开发自己的web页面,然后调用gwt的工具将它翻译成js代码.
   gwt另外一个好处在于在Ajax应用中使用RPC消除了显式地处理XMLHttpRequest和相关联的服务器返回值的需要,因为GWT对象会为你处理这些通讯工作。gwt采取了一种和老式ejb很像的方式来处理客户端和服务端的异步通讯, 当你开发一个需要被客户端调用的服务时,你同时还需要开发两个接口:
		
				 package
				 com.dyerac.client;
				package
				 com.dyerac.client;
 import
				 com.google.gwt.user.client.rpc.RemoteService;
				import
				 com.google.gwt.user.client.rpc.RemoteService;


 public
				 
				interface
				 InputService 
				extends
				 RemoteService
				public
				 
				interface
				 InputService 
				extends
				 RemoteService
				
						 {
				
				
						{
 public
						 String checkInput(String req);
   
						public
						 String checkInput(String req);
 }
}
				
				
						
						 
				
		 
		
				
				
服务接口必须扩展GWT接口RemoteService。它只定义了一个方法 checkInput(String req);
你还需要定义一个接口,客户端或最终被下载的JavaScript代码将会用它调用服务方法。GWT使用了一种回调设计模式,当我给出客户端代码的时候会再来描述它(请看MyInput.java)。
		
				 package
				 com.dyerac.client;
				package
				 com.dyerac.client;

 import
				 com.google.gwt.user.client.rpc.AsyncCallback;
				import
				 com.google.gwt.user.client.rpc.AsyncCallback;


 public
				 
				interface
				 InputServiceAsync
				public
				 
				interface
				 InputServiceAsync
				
						 {
				
				
						{
 public
						 
						void
						 checkInput(String req, AsyncCallback callable);
    
						public
						 
						void
						 checkInput(String req, AsyncCallback callable);
 }
}
				
		 
		要使一个服务在GWT中可用必须遵守命名约定;在服务接口名(InputService)后增加Async后缀。AsyncCallback对象是GWT API的一部分,它的目的是为客户端处理服务响应。不管怎样,等你看了这段代码应用的地方后会对这一行为有更清晰的了解。这些对象定义都位于用于生成客户端JavaScript的Java代码中。
最后,你需要定义一个Java类来实现远程服务接口。这个类将会存在于你的Ajax应用程序的服务器端。
 package com.dyerac.server;
package com.dyerac.server;

 import com.dyerac.client.InputService;
import com.dyerac.client.InputService;
 import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

 public class InputServiceImpl extends RemoteServiceServlet implements
public class InputServiceImpl extends RemoteServiceServlet implements

 InputService
        InputService  {
{


 public String checkInput(String req)
    public String checkInput(String req)  {
{
 // TODO Auto-generated method stub
        // TODO Auto-generated method stub
 String buf;
        String buf;
 if (req.equalsIgnoreCase("dyerac"))
        if (req.equalsIgnoreCase("dyerac"))
 buf = "sorry, this id has been registered";
            buf = "sorry, this id has been registered";
 else
        else
 buf = "Hello, " + req;
            buf = "Hello, " + req;
 return buf;
        return buf;
 }
    }

 }
}

该类必须扩展RemoteServiceServlet,这是一个GWT API对象,它本身扩展了javax.servlet.http.HttpServlet。也就是说,该类和它实现的接口需要被部署到你的servlet容器.(注: 在tomcat下部署的时候,需要删除gwt-user.jar中的javax部分)
现在,服务已经定义完毕了,只剩下对应页面的java类没有开发:
 package com.dyerac.client;
package com.dyerac.client;

 import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.EntryPoint;
 import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.GWT;
 import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
 import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.ClickListener;
 import com.google.gwt.user.client.ui.FocusListener;
import com.google.gwt.user.client.ui.FocusListener;
 import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Grid;
 import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.RootPanel;
 import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.TextBox;
 import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.Widget;


 public class MyInput implements EntryPoint, ClickListener, FocusListener
public class MyInput implements EntryPoint, ClickListener, FocusListener  {
{

 Button submit = new Button("test ajax");
    Button submit = new Button("test ajax");


 /** *//**
    /** *//**
 * 输入框
     * 输入框
 */
     */
 TextBox input = new TextBox();
    TextBox input = new TextBox();


 /** *//**
    /** *//**
 * 错误提示标签
     * 错误提示标签
 */
     */
 Label message = new Label();
    Label message = new Label();


 /** *//**
    /** *//**
 * 一个Grid对象;实际上,是一个HTML table
     * 一个Grid对象;实际上,是一个HTML table
 */
     */
 Grid grid = new Grid(2, 2);
    Grid grid = new Grid(2, 2);


 /** *//**
    /** *//**
 * 判断是否是第一次得到焦点.只有第一次得到焦点后,才会在失去焦点时进行校验
     * 判断是否是第一次得到焦点.只有第一次得到焦点后,才会在失去焦点时进行校验
 */
     */
 boolean isFirstFocused = false;
    boolean isFirstFocused = false;


 /**//*
    /**//*
 * 当浏览器载入应用程序时本方法被调用。 本方法添加标签和文本框,以及一个 按钮到页面上,并为他们注册监听器
     * 当浏览器载入应用程序时本方法被调用。 本方法添加标签和文本框,以及一个 按钮到页面上,并为他们注册监听器
 */
     */

 public void onModuleLoad()
    public void onModuleLoad()  {
{
 // TODO Auto-generated method stub
        // TODO Auto-generated method stub
 grid.setWidget(0, 0, input);
        grid.setWidget(0, 0, input);
 grid.setWidget(0, 1, message);
        grid.setWidget(0, 1, message);
 grid.setWidget(1, 0, submit);
        grid.setWidget(1, 0, submit);
 // 添加点击监听器
        // 添加点击监听器
 submit.addClickListener(this);
        submit.addClickListener(this);
 // 添加得失焦点监听器
        // 添加得失焦点监听器
 input.addFocusListener(this);
        input.addFocusListener(this);
 RootPanel.get().add(grid);
        RootPanel.get().add(grid);
 }
    }


 public void onClick(Widget sender)
    public void onClick(Widget sender)  {
{
 // TODO Auto-generated method stub
        // TODO Auto-generated method stub
 check();
        check();
 }
    }


 /** *//**
    /** *//**
 * 校验输入框中内容,只有内容非空且不为dyerac时, 才会显示正确欢迎信息
     * 校验输入框中内容,只有内容非空且不为dyerac时, 才会显示正确欢迎信息
 */
     */

 public void check()
    public void check()  {
{
 // 为服务器端服务创建一个客户端存根的实例
        // 为服务器端服务创建一个客户端存根的实例
 InputServiceAsync inputService = (InputServiceAsync) GWT
        InputServiceAsync inputService = (InputServiceAsync) GWT
 .create(InputService.class);
                .create(InputService.class);
 ServiceDefTarget target = (ServiceDefTarget) inputService;
        ServiceDefTarget target = (ServiceDefTarget) inputService;
 // 下面的"/inputservice"表示之前开发的InputServiceImpl在web应用中对应的servlet映射
        // 下面的"/inputservice"表示之前开发的InputServiceImpl在web应用中对应的servlet映射
 // 即web.xml配置的内容.
        // 即web.xml配置的内容.
 // 在开发中,也可以在MyInput.gwt.xml中进行配置
        // 在开发中,也可以在MyInput.gwt.xml中进行配置
 target.setServiceEntryPoint("/inputservice");
        target.setServiceEntryPoint("/inputservice");


 AsyncCallback callback = new AsyncCallback()
        AsyncCallback callback = new AsyncCallback()  {
{

 public void onSuccess(Object result)
            public void onSuccess(Object result)  {
{
 String s = (String) result;
                String s = (String) result;

 if (s.startsWith("H"))
                if (s.startsWith("H"))  {
{
 if (message.getStyleName().equalsIgnoreCase("warning"))
                    if (message.getStyleName().equalsIgnoreCase("warning"))
 message.removeStyleName("WARNING");
                        message.removeStyleName("WARNING");
 message.setText(s);
                    message.setText(s);

 } else
                } else  {
{
 message.setStyleName("WARNING");
                    message.setStyleName("WARNING");
 message.setText(s);
                    message.setText(s);
 }
                }
 }
            }


 public void onFailure(Throwable caught)
            public void onFailure(Throwable caught)  {
{
 message.setStyleName("WARNING");
                message.setStyleName("WARNING");
 message.setText("found a error: " + caught.toString());
                message.setText("found a error: " + caught.toString());
 }
            }
 };
        };
 String tmp = input.getText();
        String tmp = input.getText();
 // 先检验输入是否为空
        // 先检验输入是否为空

 if (tmp.length() < 1)
        if (tmp.length() < 1)  {
{
 message.setStyleName("WARNING");
            message.setStyleName("WARNING");
 message.setText("A textbox had invalid content such "
            message.setText("A textbox had invalid content such "
 + "as a blank entry.");
                    + "as a blank entry.");

 } else
        } else  {
{
 // 调用服务器端方法
            // 调用服务器端方法
 inputService.checkInput(tmp, callback);
            inputService.checkInput(tmp, callback);
 }
        }
 }
    }


 public void onFocus(Widget sender)
    public void onFocus(Widget sender)  {
{
 // TODO Auto-generated method stub
        // TODO Auto-generated method stub
 if (isFirstFocused == false)
        if (isFirstFocused == false)
 isFirstFocused = true;
            isFirstFocused = true;
 message.setText("");
        message.setText("");
 }
    }


 public void onLostFocus(Widget sender)
    public void onLostFocus(Widget sender)  {
{
 // TODO Auto-generated method stub
        // TODO Auto-generated method stub

 if (isFirstFocused == true)
        if (isFirstFocused == true)  {
{
 check();
            check();
 }
        }
 }
    }
 }
}

 
怎么样,是不是像开发swing一样简单? 再说明一下,本类中的onModuleLoad()方法就如同普通java类中的main方法,是整个程序的切入口.
代码开发完后,就要调用gwt工具进行编译(把java代码翻译成js代码),这里,只有MyInput.java是针对页面开发的,所以我们只需要翻译它.还是在命令行下进入%GWT_HOME%/gwt-workspace/gwtTrial, 输入命令applicationCreator -eclipse gwtTrial -ignore com.dyerac.client.MyInput (-ignore表示该类已存在,不需要重新创建)
随后刷新eclipse下的工程,发现结构如下:
注:下图中DialogBoxExample有关的内容是我另外开发

    MyInput-shell.cmd是在宿主模式(Hosted Mode)下启动程序,宿主模式是指我们和没有转换为Ajax应用的GWT应用交互的状态。当我们开发和调试时,我们就一直处在宿主模式下。在这种情况下,Java虚拟机使用GWT内置的浏览器运行GWT应用编译后的class内容,因此能够提供"编码、测试、调试"过程的最佳速度。
    对应的还有Web模式,是指已经成功转化为Ajax应用的状态,这种状态下,我们已经开始通过Web方式来访问Ajax应用了。在Web模式下运行时,不再需要GWT工具包或者JVM的支持。启动web模式需要用MyInput-compile.cmd先编译整个应用.
  
   另外,我们还需要修改一下MyInput.gwt.xml:
   添加<servlet path="/inputservice" class="com.dyerac.server.InputServiceImpl" /> 
 <module>
<module>

 <!-- Inherit the core Web Toolkit stuff.                  -->
    <!-- Inherit the core Web Toolkit stuff.                  -->
 <inherits name='com.google.gwt.user.User'/>
    <inherits name='com.google.gwt.user.User'/>

 <!-- Specify the app entry point class.                   -->
    <!-- Specify the app entry point class.                   -->
 <entry-point class='com.dyerac.client.MyInput'/>
    <entry-point class='com.dyerac.client.MyInput'/>
 
    
 <servlet path="/inputservice" class="com.dyerac.server.InputServiceImpl" />
    <servlet path="/inputservice" class="com.dyerac.server.InputServiceImpl" /> 
 </module>
</module>

最后,为了调试方便,我们可以直接点击MyInput-shell.cmd,在宿主模式下运行程序

4.一些困惑: 
   虽然gwt很是方便,但我觉得它自身就是一个完整的体系了,因此如何与现有框架进行整合还是个问题.比如,如何与jsf整合呢?还望各位高手支招
   而且gwt还是缺乏一款强大的ide支持,如果实现页面的wysiwyg就方便多了.另外由命令行编译也不是很方便
呵呵,期待gwt下一个版本^