[Java] Google Web Toolkit 信息汇总

博客首页 » Java Google Web Toolkit 信息汇总

发布于 08 Dec 2014 08:24
标签 blog
Google Web Toolkit 信息汇总

http://www.itcao.com/post_tag/gwt
飞尘@IT草

http://paris8.org/a/bbs/viewthread.php?tid=4109&extra=&page=2

GWT 项目经验总结与感想

最近接手了一个GWT的项目,因为公司从来就没有做过这个技术的项目,所以我必须负责所有的关于GWT的Research以及Design的工作。我很早以前就对于Google那帮天才所作的OpenSource项目非常感兴趣了,可惜一直没有机会接触,这次要我从头学起GWT,我当然是乐在其中咯。不过也因为是从头学起所以走了许许多多弯路!在这篇文章中我就和大家分享一下我的GWT项目经验与感想。

GWT编程经验总结(GWT客户端的限制)

•GWT 客户机代码必须与 Java 1.4 兼容。那意味着别指望在GWT client-side里使用类属性(泛型)、新样式的 for 循环等等5.0的新特征。 

•在GWT客户端中只有极少的 Java 标准库受到支持。受支持的库类限于 java.util 包和 java.lang 包。但并不是所有这些包都支持。举例来说Java.lang.reflect 包就不被支持(无法使用Proxy, Method等功能)。也就是说Java的反射机制在GWT的客户端中是不会通过编译的。(其实很容易考虑,你只要想想Javascript有没有这个功能。因为所有的Client-side的Java代码都将编译成Javascript) 

•对于序列化的客户机类也有一些限制,Java的Serialization是不被支持的。该类可以通过实现接口com.google.gwt.user.client.rpc.IsSerializable来实现。所有JavaBean必须继承GWT的序列化接口。(实现客户端与服务器端之间的JavaBean交互数据) 

•因为 JavaScript 语言是不支持线程。所以在GWT 编译程序中会忽略 synchronized 声明。当然,多线程的使用也别指望了。 

•Object.getClass().getName() 也是不支持的。要使用GWT.getTypeName(Object)才可以得到对象类型。 
•GWT允许自定义Java-Javascript编译器,如果你有兴趣可以研究一下。反正作到现在我觉得没那个必要。

•支持自定义控件库。做过J2EE客户端的朋友一定有这样的经验,喜欢把所有的taglib放入一个单独的project中。从而实现一个公司内部的taglib 库,在以后的项目中我们可以重用这些taglib. GWT其实也可以这样的。但是你需要定义一个控件库的*.gwt.xml。并且在你的GWT项目的*.gwt.xml 中inherits。 

•不可能将所有的服务器端接口合并为一个Factory,

Example:

public static Object getGWTService(Class service, String ServiceURI) { 
 Object instance = GWT.create(service); 
 ServiceDefTarget target = (ServiceDefTarget) instance; 
 target.setServiceEntryPoint(GWT.getModuleBaseURL() + ServiceURI); 
 return instance; 
} 

这种方式是行不通的,我尝试了很久希望能够通过统一的接口来获得RPC Service,可是当编译Java to Javascript时会报出Only class literals may be used as arguments to GWT.create()的Error message, 我的理解就是GWT.create(..)中的参数不可以申明为一个变量的interface class.可能在转换为Javascript时会出错。具体如何的内部机制无从考证。(这个GWT做得我都想哭了,我喜欢的设计方式都这样莫名其妙的被抹杀了)
总之,以上为GWT客户端的限制,虽然有些麻烦,但是由于GWT客户端编译时要进行Java到Javascript之间的转换的原因,避免了你去开发那更烦人Javascript。而且这样一来可是使得客户端,服务器端的层次变得更清晰。因为我们无法把大量的逻辑代码写入客户端。层次的明晰使得维护,测试变得简便。总而言之,只要在客户端开发时注意我以上提到的这些注意事项,把大量的逻辑代码放入服务器端,你会发现其实GWT还是不错的!

参考:http://code.google.com/webtoolkit/documentation/com.google.gwt.doc.DeveloperGuide.Fundamentals.JavaToJavaScriptCompiler.LanguageSupport.html 

整合IOC和GWT

用过GWT的朋友一定知道,要在GWT 中定义一个Remote service就必须在*.gwt.xml中定义这样一段servlet声明。
可是这样的定义灵活性不够,而且无法做到将外部的Java file定义为Servlet

项目中我们会希望使用IOC工具来实现Remote service的申明。并且实现把所有Service定义为外部Jar(方便逻辑层的Junit测试)。这种方法不同于标准的GWT开发的,在此不是要提倡,只是提出另一种实现的方式而已。

我在我的项目中就使用了IOC和GWT的整合。我要让GWT整合我公司内部开发的IOC工具,而且还要整合Spring和Hivemind.这些流行的IOC工具(痛苦的过程),这里就不一一举例了。不过我要提一下Spring和GWT的整合。通过Spring 的DispatcherServlet来分配Server-side RPC servlet.这样简单就整合了这两个框架。怎么让那些老外想出来的?太有才了。有研究这方面内容的朋友可以参考dengyin2000的这篇文章http://www.javaeye.com/topic/58084 。这里就不重复了。感谢dengyin2000的技术分享。官方网站是:http://gwt-widget.sourceforge.net/?q=node/39

部署GWT到Tomcat中

在我研究GWT时,看到很多网站都有人提问如何在Tomcat中部署GWT 。其实使用Ant打包GWT成一个war file再放入Tomcat中就可以了。我把我GWTTest项目中的build.xml文件发出来,有需要的朋友可以下载看一下,xml内有详细的注释。只要把XML中我打”*”号的部分改为你的GWT项目中相应的内容就可以直接使用Ant打包您的GWT project成war 文件了。

Echo和GWT

网上已经有非常详细的文章来比较这两个 AJAX框架了。这里就不重复了,有兴趣的朋友可以google一下。我之前也做过一个Echo2的项目,并且写了一篇Echo2的分析文章来介绍Echo2。我这里就从一个开发人员的角度来简单的分析一下这两框架。其实从根本的底层技术上来讲,两者根本就没有可比性。GWT是把客户端Java 代码转换为JAVASCRIPT,再使用RPC调用服务器端逻辑层Servlet。而Echo2应该就是建立在Servlet上的一个框架,具体的核心实现方式我不了解。不过这两种技术都有一个共同点——太有才了。都是全ajax的web框架,而且你基本不用理那烦人的JAVASCRIPT。视图层使用类似于javascript的Java编写方式就是最大的一个创新。从我个人的角度来说我更倾向于GWT,因为Echo的性能实在有些不敢恭维(不过我相信如果未来的Echo3能够提升性能的话,Echo会变得更出色)。

看了一些Javaeye中其他关于GWT的文章。感到很多人是因为GWT有太多有别于一般意义上的java语言的特性才对GWT产生了一些误解。抱歉,我这里不是在针对谁,毕竟我相信除了Google之外很少会有公司运用这个框架来开发项目。很多技术如果不是在实际项目中运用,仅凭自学是很难深入了解的。当然我并不是说GWT有多好,他也有局限性。坦率说我更喜欢wicket,个人觉得他融入了部分的GWT的客户端开发创意,而且很好的弥补了GWT中的一些不足之处。我喜欢这些有创意的开发理念,不断涌现的新创意会让Java这一技术更有意思。
收藏分享评分
回复 引用
订阅 TOP

GWT 1.5: 支持Java 5、性能改进及JavaScript增强

用来创建基于JavaScript web应用的以Java为中心的编译器Google Web Toolkit(GWT),今天(8月28日)它发布了1.5版。InfoQ有幸采访了其技术领导Bruce Johnson以了解关于该发布的更多信息及增加的新特性。

GWT 1.5的主要新特性包括:
Java 5语言支持——支持Java 5的特性如泛型、枚举、注解、自动装箱及可变参数列表
编译优化——编译后的应用的性能再一次得到改进,现有的应用使用GWT1.5重新编译后速度将有很大提升
JavaScript增强——JavaScript的Overlay类型及新的高性能的DOM API提供了强类型的JavaScript对象和DOM编程,而这一切无需增加运行时成本
默认的可视化主题——新版本提供了几个基于CSS的UI主题以初始化应用的感官(look-and-feel)

可以在这里查看完全的变化列表及发布信息。

Johnson阐述了1.5版的几个变化:

[@gwt.eventArgs以前用作]针对RPC的基于javadoc的元数据。这是一个很棒的示例,因为当升级到GWT 1.5时,大多数人一开始都会遇到这个变化。RPC现在变得更简单也更丰富,因为RPC接口可以简单地指定泛型集合。另一个值得注意的事情就是GWT 1.5现在已经完全支持“long”原始数据类型了。在GWT 1.5之前,“long”并没有得到真正支持,因为JavaScript没有64位的整数类型,因此使用本地的JS number并不能表示“long”的全部范围。在GWT 1.5中,我们通过产生额外的代码来确保long的行为是完全正确的,尽管在性能上要比GWT 1.4稍微低点。这是不可避免的:我们必须在性能和正确性上做出抉择,显然我们会选择后者。如果你在GWT 1.4中大量使用了long类型并且速度要比数字范围更加重要的话,请考虑将这些变量改为“int”或者“double”以保持与GWT 1.4一样的速度。

Johnson还详细分析了编译器的变化——以此来支持1.5版:

GWT编译器前端重用了Eclipse提供的优秀的Java编译器。它处理所有的解析和语法检查,然后构建一个抽象的语法树(abstract syntax tree,即AST)。接下来我们使用该AST进行优化并生成JavaScript输出。Eclipse编译器让我们变得更加轻松,因为它处理了大量工作。但是,寻求在JavaScript中有效地表达新概念的方式,如枚举和增强的for循环,仍然花费了我们大量的工作。新的语言特性还对GWT库起到了积极的连锁反应,最明显的就是在RPC中能够使用泛型这个新功能,我们还对注解进行了扩展以在下面这些情况中代替基于javadoc的元数据方式:国际化、图像包和基准。 

Johnson说到GWT在web开发方面的主要竞争对手可以分为三类,同时也谈到了GWT该如何使自己有别于这些竞争对手:

  • 非DOM的UI模型,如Flex——GWT使用浏览器的DOM模型而并不想代替它,同时Johnson指出GWT已经实现了一些非常棒的Flash和DOM UI的集成
  • 服务器端的HTML生成器,如JSF——GWT对其是个补充,因为它是纯粹的客户端技术(除了RPC之外),并且它与服务器端组件集成的很好,然而Johnson指出更加丰富、更具状态的GWT客户端代码使我们可以构建更具响应性和流畅的UI,从而降低了对服务器端的等待
  • AJAX库,如Dojo与jQuery——这儿的主要区别在于编程语言的选择;GWT使用现有的Java开发工具和编译器功能来产生高度优化的JavaScript代码。我们可以同时使用Java和JavaScript,然而Johnson说到Java使用的越多,GWT编译器的优化程度也就越高

当被问到GWT与Google的其他工程的整合时,Johnson说到:
关键点:GWT给予你很多,但是它不想变成一个“围墙花园(walled garden)”。任何抽象都会有遗漏的东西,所以最好接受这个事实。我们有意简化该抽象,这样你就可以触及JavaScript的具体细节,那么你就可以集成你喜欢的任何其他技术了。这种灵活性对于GWT本身和其用户来说都是一种保险单:你可以确定你能将任何客户端技术与GWT集成,同时我们(GWT开发小组)也不必显式地将其与开放的东西进行集成,因为你总是可以自己完成这件事而不必等我们来完成。对于我所提到的灵活性的示例,请参考Ray Cromwell的工作——Syndroid。 

Johnson还说有很多与GWT的集成,包括将其集成到Spring、Flash以及大量的Google Code projects。

当被问到GWT未来的计划时,Johnson说到:
我们将继续关注性能改进以及将要从GWT孵化器中出来的几个新widget,包括一个日历控件和几个漂亮的表格widget。我们还在着手做其它完全新鲜的东西,如声明式的基于XHTML的UI模板机制。我们尚未计划好一切,但我期望在随后一个或两个版本中能将其加进来。顺便提一下,我希望它的开发周期比GWT 1.5更短。

Johnson还希望能将GWT作为Google App Engine的一个开发环境,要是这样的话,那真是太酷了。

查看英文原文:GWT 1.5: Java 5 Support, Performance Improvements and JavaScript Enhancements

http://www.bwcsc.net/?q=node/86

Gwt教程之创建UI

创建UI 

现在我们决定StockWatcher 有那些功能,之前我们已经讨论了如何编写让GWT 编译器可编译的Java 源代码 。下面我们开始构建应用程序的UI 。

GWT 的UI 是由 widgets   和 panels 组成的 。Widgets 提供了通用UI 元素模型如buttons , text boxes , 和trees。Panels, 比如 DockPanel , HorizontalPanel , 和RootPanel , 包含widgets ,用于界定在浏览器中它们是如何布局的。你有很多种方式自定义widgets 。当然,你也可以在网上找到好的第三方widget 库   。

Widgets 和panels 在所以的浏览器上工作方式一模一样。通过使用它们,你大可不必为每个浏览器写专门的代码。GWT 有一套完整的widgets 组件是可用的且"out of the box, " ,当然,你可以不必局限于GWT 给你提供的那些。

RootPanel 

任何GWT UI 层次的上方,都有一个 RootPanel 。RootPanel 中通常包装了 HTML host page 中的一个实际元素。默认的RootPanel 包装的是the HTML host page 的<body> 元素。你可以通过调用 RootPanel.get() 方法来获得它的引用,你也可以获得页面中的其他元素。只需要指定那些页面元素的属性id ,然后把它传到 RootPanel.get(String) 方法中。

因此,你有2 种选择来构建你的GWT 应用程序的界面。你可以使用正常的静态HTML 构造应用程序界面,只需要在名为placeholder 元素处插入GWT widgets 和panels 。这种方式在集成已有程序时非常有用,此外,你的网页可以包含空的<body> 元素, 这样你就可以获得默认的RootPanel 的引用,然后在此基础上构建完全的GWT widgets 界面。

在 StockWatcher 例子中, 我们要在我们的HTML 中使用一个 placeholder 元素 。继续打开网页文件(src/com/google/gwt/sample/stockwatcher/public/StockWatcher.html) ,用下面的HTML 替换掉它的内容:
<!DOCTYPE HTML PUBLIC "-W3CDTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>StockWatcher</title>
    <script type="text/javascript" language="javascript" src="com.google.gwt.sample.stockwatcher.StockWatcher.nocache.js"></script>
  </head>

  <body>
    <h1>Stock Watcher</h1>
    <div id="stockList"></div>
  </body>
</html>

这段代码定义了页面的静态内容。和正常的html 一样,<title> 标签显示的是在浏览器中页面的标题。<script> 引用的是GWT 将要生成的JavaScript 源码。最后,<div> 标签包含了我们的GWT widgets 。这里设定它的id 属性是为了我们能够在GWT 中通过RootPanel 来访问它。

另外,在文件上方的HTML 4.01 Transitional DOCTYPE 声明设定了要渲染的引擎为"Standards Mode 标准模式" 它提供了更好的跨浏览器能力。如果你移除掉此行,页面将以"Quirks Mode" 渲染,这也就意味着老的浏览器问题都将存在。在某种情况下,如果你要集成的程序依赖于某个浏览器的时候,你可能会选择"Quirks Mode" 。但现在,我们使用的是"Standards Mode".

Panels and Widgets 

现在我们需要使用GWT panels and widgets 来构建UI 的动态部分。the Getting Started guide 的 开始 向你展示的是StockWatcher 的最终效果。库存列表以表格的形式显示,下方是新文本框和增加按钮,底部显示的是最近更新的时间戳。由于UI 元素是垂直的堆栈形式。我们查看 widget gallery 会发现 VerticalPanel   就是我们需要的,我们要使用GWT VerticalPanel 和它的三个孩子节点。
第一个节点是库存列表自己。由于这是一个表格,我们再回到gallery 来查找HTMLTable ,页面显示的那样。他是抽象标示所以我们需要查找合适的子类。Grid 表格不会起作用,因为它不运行我们从表格中间移除一行( 我们需要从列表中移除库存的功能) 。另外  ,FlexTable 确实有一个removeRow(int) 方法,它也有方法设定单元格内容(按行列索引),如果需要的话可以自动扩展表格。迟早会有用的。

VerticalPanel 上的第二个节点是一个新文本框标记和增加按钮。我们想让它们显示在同一行,所以需要另外一个内嵌的panel 来完成布局。因此,我们使用HorizontalPanel   ,且TextBox 和Button 作为其子节点。
最后, VerticalPanel 的第三个节点是最后更新的时间戳,它显示了一个简单的Label 。这个Label 窗口被设计成一个动态显示的非html 文本。 
之后,看代码: 打开 StockWatcher 的切入点类( entry point class ) (src/com/google/gwt/sample/stockwatcher/client/StockWatcher.java) 用如下代码替换:

package com.google.gwt.sample.stockwatcher.client;
 
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
 
public class StockWatcher implements EntryPoint {
 
  private VerticalPanel mainPanel = new VerticalPanel();  
  private FlexTable stocksFlexTable = new FlexTable();
  private HorizontalPanel addPanel = new HorizontalPanel();
  private TextBox newSymbolTextBox = new TextBox();
  private Button addButton = new Button("Add");
  private Label lastUpdatedLabel = new Label();
 
  public void onModuleLoad() {
    // set up stock list table
    stocksFlexTable.setText(0, 0, "Symbol");
    stocksFlexTable.setText(0, 1, "Price");
    stocksFlexTable.setText(0, 2, "Change");
    stocksFlexTable.setText(0, 3, "Remove");
 
     // assemble Add Stock panel
    addPanel.add(newSymbolTextBox);
    addPanel.add(addButton);
   
     // assemble main panel
    mainPanel.add(stocksFlexTable);
    mainPanel.add(addPanel);
    mainPanel.add(lastUpdatedLabel);
   
     // add the main panel to the HTML element with the id "stockList"
    RootPanel.get("stockList").add(mainPanel);
   
     // move cursor focus to the text box
    newSymbolTextBox.setFocus(true);
  }
}

我们所做的仅是使用GWT 的 widets 和panels 构建一个简单的界面而已。我们是 自底向上实现的, 首先通过类属性来初始化每个widget/panel 。之后在onModuleLoad() 方法中,我们设定了FlexTable 的首行,并把它的各个子节点加到panels 上。最后一步通过我们的HTML 页面上名为stockList 的<div> 元素,把我们外部的VerticalPanel 增加到RootPanel 上。

测试界面 

是时候来测试一下我们做的改变了。保存已编辑的文件,在hosted 模式下运行StockWatcher (在eclipse 中,点击Run 按钮,如果你使用其他IDE 的话,运行 StockWatcher-shell ),你会看到StockWatcher 在hosted 模式下下浏览的原始形式。 
正如你所看到的,当前我们所做的不是为了拿任何的设计奖,演示而已,我们之后会增加一些CSS 。StockWatcher 真正缺乏的是可交互性:该界面实际上暂时没有做任何事—— 让我们来通过增加一些事件监听器 完成它吧!

GWT教程之 增加事件监听器

和许多UI 框架一样,GWT 是基于事件驱动的。这意味着所以代码的执行要由其他事件做出响应。常常该事件由用户来触发,使用鼠标或键盘和应用程序接口来交互。

监听接口 

在你响应一个事件之初,你先要告诉GWT 你对哪种类型的事件感兴趣。这称为事件注册。为注册一个事件,你需要给应用程序窗口指定一个特殊的监听器。当它发生的时候来声明一个事件,此监听器定义了一个或多个稍后要调用的方法。从窗口的视角来看,我们说窗口触发了一个可以前往任何监听器的事件。

注册事件 

在GWT 中,可以使用很多不同的接口。例如,ClickListener 接口就是一个简单的单击事件的事件监听器。它仅仅包含了一个方法,onClick(Widget) 。当用户点击一个窗口声明单击事件的时候就会调用该方法。这样的窗口可以是一个Button   类。下面通过实践来弄清它,让我们返回到StockWatcher 例子中。在增加库存清单上我们已经建了一个按钮。现在我们通过调用ClickListener 接口来处理它的单击click 事件。

为此我们需要在适当的位置添加addClickListener(ClickListener) 的监听器。让我们继续在onModuleLoad() 方法中调用上面那个方法:

public void onModuleLoad() {
  // 设定库存类表的显示
  stocksFlexTable.setText(0, 0, "Symbol");
  stocksFlexTable.setText(0, 1, "Price");
  stocksFlexTable.setText(0, 2, "Change");
  stocksFlexTable.setText(0, 3, "Remove");
 
  // 为新增库存设定事件监听器
  addButton.addClickListener(new ClickListener() {
    public void onClick(Widget sender) {
      addStock();
    }
  });
 
  // 放到库存面板上。
  addPanel.add(newSymbolTextBox);
  addPanel.add(addButton);
 
  // assemble main panel
  mainPanel.add(stocksFlexTable);
  mainPanel.add(addPanel);
  mainPanel.add(lastUpdatedLabel);
 
  // 在HTML 元素id 为"stockList" 处嵌入主面板  RootPanel.get("stockList").add(mainPanel);
   
  // 将光标置于text box 中
  newSymbolTextBox.setFocus(true);
}
 
private void addStock() {
  // 当用户点击按钮时的执行的代码
}

你也需要在StockWatcher.java 头部增加2 条import 语句:
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Widget;
你可能注意到我们使用的实现ClickListener 的是匿名内部类。其内部定义了onClick(Widget)   方法,委托给一个新方法addStock() 。实际上。addStock() 才是我们验证库存符号和增加试图列表起作用的。

对于小一点的应用程序来说仅需要处理一些数目很小的相关的事件,这时使用匿名内部类足以完成这些小问题。但如果我们要对大量窗口注册事件,这种方法就没效率了,因为它导致了过多的独立监听器对象被创建。在这种情况下,对多个事件源来说指定包含的类实现监听器接口和使用独立的接口方法是比较好的方式。当窗口调用方法的时候,建议发送参数,这样你就可以知道哪个触发了该事件。这可以有效的使用内存但可能会多些代码,比如下面的例子:

public class ListenerExample extends Composite implements ClickListener {
  private FlowPanel fp = new FlowPanel();
  private Button b1 = new Button("Button 1");
  private Button b2 = new Button("Button 2");
 
  public ListenerExample() {
    initWidget(fp);
    fp.add(b1);
    fp.add(b2);
    b1.addClickListener(this);
    b2.addClickListener(this);
  }
 
  public void onClick(Widget sender) {
    if (sender == b1) {
      // handle b1 being clicked
    } else if (sender == b2) {
      // handle b2 being clicked
    }
  }
}

监听器适配器 
现在回到StockWatcher 例子中. 如果我们在下部分中实现了addStock() 方法,用户就可以点击按钮来增加库存信息到列表中。为了方便起见,我们可以允许用户在newSymbolTextBox 内部使用键盘或通过敲击Enter 回车。为了达到这个目的,我们需要注册newSymbolTextBox's 的键盘事件通过KeyboardListener   中的addKeyboardListener(KeyboardListener) 方法。
查看KeyboardListener 接口,我们可以看出它包含了3 个监听方法: 
onKeyDown(Widget, char, int)   Fired when the user depresses a physical key.
onKeyPress(Widget, char, int)   Fired when a keyboard action generates a character.
onKeyUp(Widget, char, int)   Fired when the user releases a physical key.
在我们的例子中这好像太强大了,:我们只需要onKeyDown(Widget, char, int) 监听方法。我们需要一个类来允许我们绑定这个特殊的我们感兴趣的事件,忽略其他的。这也证明,GWT 为此目的建立了适配器类。Adapters 是简单的,空的,确定的一个特殊事件接口的实现。仅子类有适配器类,并对你想要注册的事件提供实现。
在StockWatcher 例子中, 我们实现了 KeyboardListenerAdapter   接口并且重写了onKeyDown(Widget, char, int)   方法. 增加import:
import com.google.gwt.user.client.ui.KeyboardListenerAdapter;
之后在onModuleLoad() 方法中增加调用 addKeyboardListener(KeyboardListener) 具体如下:

// set up event listeners for adding a new stock
addButton.addClickListener(new ClickListener() {
  public void onClick(Widget sender) {
    addStock();
  }
});
 
newSymbolTextBox.addKeyboardListener(new KeyboardListenerAdapter() {
  @Override
  public void onKeyDown(Widget sender, char keyCode, int modifiers) {
    if (keyCode == KEY_ENTER) {
      addStock();
    }
  }
});
 
// assemble Add Stock panel
addPanel.add(newSymbolTextBox);
addPanel.add(addButton);

现在我们的事件监听器已整装待发,当我们要处理事件的时候,下一步将需要填写我们那个空的 addStock() 方法来增加库存信息到列表中。
原文出处http://code.google.com/intl/zh-CN/docreader/#p=google-web-toolkit-doc-1-5&s=google-web-toolkit-doc-1-5&t=GettingStartedEvents

http://www.bwcsc.net/?q=node/88

GWT 1.6 的新功能

将在2009 年Q1 发布的GWT 1.6 将带来一些新的功能,有些功能还在讨论中,有些已经可以确定了.

新的编译文件部署结构
更容易的编译成war 形式. 让你可以更容易在标准的servlet 容器里部署
默认的hosted mode 的容器从tomcat 转向jetty
相信不用解释原因吧
统一的event handler
新的event handler 将会实现统一的格式,对所有widgets 都一致的格式,监听器会有重复.
新的组件DatePicker , LazyPanel
字符串性能提升
StringBuilder 使用延迟绑定提高字符串添加浏览器
编译性能提升
GWT 1.6 将会引进新的并行编译, 现在的GWT 编译其实消耗的内存还是比较大的.

在1.6 之后还在讨论的新功能
Developer Guided Code splitting
Developer guided code splitting is a mechanism that allows developers to specify asynchronous split points in their code where the code base can be split and downloaded in different chunks. This is currently an R&D project but looks promising.
Analysis of compiled code, aka Story of your compile (SOYC)
Aims to give developers concrete information about their compiled JavaScript, such as which Java classes are generating the most JavaScript code.
In-browser hosted mode, aka Out-of-process Hosted Mode (OOPHM)
In-browser hosted mode will allow GWT developers to debug their apps within a browser rather than GWTR17;s hosted mode browser
UI Binder
The UI Binder will allow the creation of UI elements in a declarative fashion. Watch for UI Binder to land in the GWT incubator soon.
Client Bundle
Client Bundle implements the power of deferred binding used in Image Bundle in a generic fashion so that it can be used on many resources. These include TextResource, ImageResource, and CSSResource
RPC performance improvements
Ongoing work to improve the performance of RPC

http://www.bwcsc.net/?q=node/89

Google引入GWT Overlay类型

Google Web Toolkit小组就GWT overlay类型发表了一篇文章,将其作为正在进行的名为“Getting to really know GWT(真正了解GWT)”系列文章的一部分。  

GWT 1.5引入了JavaScript overlay类型以简化将整个JavaScript对象家族集成到GWT项目的过程。该技术有很多优势,如利用Java IDE的代码完成和重构能力,甚至当你在编写无类型的JavaScript对象时也可以充分利用这一优势。

这篇文章展示了如何为JSON数据创建Java bean风格的包装器:

var jsonData = [
{ "FirstName" : "Jimmy", "LastName" : "Webber" },
{ "FirstName" : "Alan", "LastName" : "Dayal" },
{ "FirstName" : "Keanu", "LastName" : "Spoon" },
{ "FirstName" : "Emily", "LastName" : "Rudnick" }
];

相应的GWT Java对象可以将JSNI方法调用与常规的Java方法结合起来,使用内置功能来产生类型安全的Java对象:

class Customer extends JavaScriptObject {
// Overlay types always have protected, zero-arg ctors
protected Customer() { } 
// Typically, methods on overlay types are JSNI
public final native String getFirstName() /*-{ return this.FirstName; }-*/;
public final native String getLastName() /*-{ return this.LastName; }-*/;
// Note, though, that methods aren't required to be JSNI
public final String getFullName() {
return getFirstName() + " " + getLastName();
}
}
class JsArray<E extends JavaScriptObject> extends JavaScriptObject {
protected JsArray() { }
public final native int length() /*-{ return this.length; }-*/;
public final native E get(int i) /*-{ return this; }-*/;
}

剩下的步骤就是将Javascript对象转化为Java对象,这是通过变量推断(variable inference)和另一个JSNI调用完成的:
private final native JsArray<Customer> getCustomers() /*-{
return $wnd.jsonData;
}-*/;

请查看原始文章以进一步了解overlay功能以及GWT编译器对其所做的优化。
查看英文原文:Google Introduces GWT Overlay Types

http://www.bwcsc.net/?q=node/91

Gwt延迟绑定介绍

Gwt的延迟绑定是一种gwt对反射机制的一种支持方案。简单的说就是在使用gwt进行代码开发的时候,可以使用接口和抽象类,而不用管它的实现,在编译后或者host模式的情况下,gwt会自己跟模块配置的信息来使用具体哪一个实现类替代代码中的接口和抽象类。
使用说明:
1.定义接口或者抽象类,然后再定义一个实现类。
2.在xxx.gwt.xml(模块文件中定义)替换的参数信息和具体替换的类
Xml代码 

<replace-withclass="com.google.gwt.user.client.impl.DOMImplMozilla">
<when-type-isclass="com.google.gwt.user.client.impl.DOMImpl"/>
<when-property-isname="user.agent"value="gecko1_8"/>
</replace-with>
<replace-withclass="com.google.gwt.user.client.impl.DOMImplMozilla">
   <when-type-isclass="com.google.gwt.user.client.impl.DOMImpl"/>
   <when-property-isname="user.agent"value="gecko1_8"/>
  </replace-with>

上面的配置是将在firefox浏览器里面使用DOMImpl该类的地方使用DOMImplMozilla这个类进行替换。其中的replace-with是指实际用到的类,when-type-is则是要替换的类,when-property-is则是一些参数信息,可以添加0到多个。另外,关于参数的配置还可以加一些逻辑的限制,如Any,
<any>
<when-property-isname="user.agent"value="gecko"/>
<when-property-isname="user.agent"value="gecko1_8"/>
</any>
<any>
<when-property-isname="user.agent"value="gecko"/>
<when-property-isname="user.agent"value="gecko1_8"/>
</any>

3.在代码中使用
通过GWT.create方法可以动态的获取不同的实现类,如下:
DOMImplimpl=(DOMImpl)GWT.create(DOMImpl.class);
再结合gwt的dom模块的配置信息(如下),impl对象将会根据不同的浏览器而动态采用不同的domimpl类的实现。
<module>
<inheritsname="com.google.gwt.core.Core"/>
<inheritsname="com.google.gwt.user.UserAgent"/>
<replace-withclass="com.google.gwt.user.client.impl.DOMImplOpera">
<when-type-isclass="com.google.gwt.user.client.impl.DOMImpl"/>
<when-property-isname="user.agent"value="opera"/>
</replace-with>
<replace-withclass="com.google.gwt.user.client.impl.DOMImplSafari">
<when-type-isclass="com.google.gwt.user.client.impl.DOMImpl"/>
<when-property-isname="user.agent"value="safari"/>
</replace-with>
<replace-withclass="com.google.gwt.user.client.impl.DOMImplIE6">
<when-type-isclass="com.google.gwt.user.client.impl.DOMImpl"/>
<when-property-isname="user.agent"value="ie6"/>
</replace-with>
<replace-withclass="com.google.gwt.user.client.impl.DOMImplMozilla">
<when-type-isclass="com.google.gwt.user.client.impl.DOMImpl"/>
<when-property-isname="user.agent"value="gecko1_8"/>
</replace-with>
<replace-withclass="com.google.gwt.user.client.impl.DOMImplMozillaOld">
<when-type-isclass="com.google.gwt.user.client.impl.DOMImpl"/>
<when-property-isname="user.agent"value="gecko"/>
</replace-with>
</module>

http://www.bwcsc.net/?q=node/99

GWT RPC例子

GWT可以将JAVA代码转换成Javascript代码,让不懂js的开发人员也能编写具有丰富客户端交互功能的web程序。但大部分程序还是要和服务器进行交互的,那么GWT是如何实现客户端与服务端交互的呢。
GWT有两种与服务端进行交互的技术。GWTRPC和基于普通AJAX的远程交互技术。这里主要介绍下GWTRPC技术。
首先,我们创建一个类来表示客户端和服务端要交互的数据类型。在GWTRPC中,如果要交互的数据是一个复杂类型的话,那么这个类要实现IsSerializable的接口,来使该类能被正常的序列话和反序列化。代码如下图所示。
1.  packagecn.edu.zju.cs.client;
2.   
3.  importcom.google.gwt.user.client.rpc.IsSerializable;
4.   
5.  public class Person implements IsSerializable{
6.  privateStringname;
7.  privateintage;
8.  publicintgetAge(){
9.  returnage;
10. }
11. publicvoidsetAge(intage){
12. this.age=age;
13. }
14. publicStringgetName(){
15. returnname;
16. }
17. publicvoidsetName(Stringname){
18. this.name=name;
19. }
20. }
接着,我们要创建一个接口,此接口要继承RemoteService接口,并添加一些方法。
1.  packagecn.edu.zju.cs.client;
2.   
3.  importcom.google.gwt.user.client.rpc.RemoteService;
4.   
5.  publicinterfaceMyServiceextendsRemoteService{
6.  publicPersongetPerson(Stringname,intage);
7.  }
接下来,创建一个该接口对应的异步接口,异步接口的方法返回值均为void,并且其对应的每个方法都比原接口多一个类型为AsyncCallback的参数。
1.  packagecn.edu.zju.cs.client;
2.   
3.  importcom.google.gwt.user.client.rpc.AsyncCallback;
4.   
5.  publicinterfaceMyServiceAsync{
6.  publicvoidgetPerson(Stringname,intage,AsyncCallbackcallback);
7.  }
然后,我们在对应的server目录下面创建一个该接口的实现类,这个实现类要继承RemoteServiceServlet类
1.  packagecn.edu.zju.cs.server;
2.   
3.  importcom.google.gwt.user.server.rpc.RemoteServiceServlet;
4.   
5.  importcn.edu.zju.cs.client.MyService;
6.  importcn.edu.zju.cs.client.Person;
7.   
8.  publicclassMyServiceImplextendsRemoteServiceServletimplementsMyService{
9.   
10. publicPersongetPerson(Stringname,intage){
11. TODOAuto-generatedmethodstub
12. Personperson=newPerson();
13. person.setName(name);
14. person.setAge(age);
15. returnperson;
16. }
17. }
18.  
接下来,我们在模块配置文件Zhen.gwt.xml中进行相应的修改,添加<servletpath="/person"class="cn.edu.zju.cs.server.MyServiceImpl"/>,使GWTShell的内置TomcatServer能找到实现类。
到这里,所有的准备工作都做完了,我们开始在客户端调用这个远程的服务。这里要注意的是不要使用硬编码的URL,因为测试时的URL和部署时的URL是不同的。
1.  MyServiceAsyncservice=(MyServiceAsync)GWT.create(MyService.class);
2.  ServiceDefTargetendpoint=(ServiceDefTarget)service;
3.  endpoint.setServiceEntryPoint(GWT.getModuleBaseURL()+"/person");
4.  
endpoint.setServiceEntryPoint("http://localhost:8889/cn.edu.zju.cs.Zhen/person");
5.   
6.  AsyncCallbackcallback=newAsyncCallback()
7.  {
8.  publicvoidonSuccess(Objectresult)
9.  {
10. Personperson=(Person)result;
11. Window.alert(person.getName());
12. }
13. publicvoidonFailure(Throwablecause)
14. {}
15. };
16. service.getPerson("zhenye",24,callback);
好了,到这里我们就完成了一个基本的GWTRPC的调用。假如我们要使用外部的server来代替GWT内置的TomcatServer,我们只要在web.xml里让客户端使用的URL对应到MyServiceImpl类即可。
我们可以用eclipse的TCP/IPMonitor将请求拦截,然后看下客户端和服务端的请求数据,我们发现在HTTP请求数据中包括了接口类的名字,请求的方法,传递的参数等等数据,服务端在有了这些数据后就能将请求定位到特定的方法并进行处理。服务端的返回数据则包括了请求数据类序列后的数据。如下所示:
//OK[2,24,1,["cn.edu.zju.cs.client.Person/2086786979","zhen\x20ye"],0,4]

http://www.bwcsc.net/?q=node/100

GWT开发的忠告调查

     GWT开发的八大忠告 Google Web工具包(GWT)确实是使用Java开发Ajax应用的一种诱人方法。如果你在AWT/Swing/SWT和服务器小程序方面有着扎实背景,实际上很容易学会使用GWT ,但如果要做的不仅仅是快速原型设计,那么有些难题仍然存在。
        据Google在GWT主页上宣布的内容显示,GWT为“不是非常熟悉”的开发人员简化了编写AJAX应用的工作。 从2006年12月的版本1.3.RC开始,采用Apache 2.0许可证的GWT完全开放了源代码。最新的稳定版本是版本1.3.3。该工具包的核心是GUI库,随带Java到JavaScript编译器、异步远程过程调用(RPC)和对象序列化机制;要是使用像Eclipse或者IntelliJ的IDEAT这些集成开发环境(IDE),还完全支持客户端和服务器端的代码调试。浏览器

  笔者为Tomcat编写了几百行的GWT及与GWT有关的服务器小程序代码,但不会自称是GWT专家,而是想探讨市面上的入门教程和文章不会提到的一些问题和解决办法。这里,假设读者朋友已经熟悉Java、GWT和服务器小程序的基本知识,并且熟悉Eclipse、Ant和JUnit等工具。

     1.分而治之
     2.调试和错误报告不仅仅只有Window.alert ()
     3.当心GWT Shell的“刷新”按钮陷阱
     4.在宿主模式下读取Servlet Init参数。
     5.在浏览器里面显示PDF文件
     6.力求获得无状态服务器
     7.使用Selenium实现GWT Web测试的自动化
     8.忠告之八:使用Groovy Gant脚本部署应用。

***

NO.1 分而治之

分而治之
  众所周知,GWT应用就是Java应用。不过,问题在于是“哪种Java”,我们得牢记:GWT编译的是与J2SE 1.4.2或者更早版本兼容的Java源代码。另外,只有J 2SE 1.4.2 API的子集得到支持,即java.lang和java.util程序包。即便在使用这些程序包时,也要非常认真地研究Google在运行库支持方面的注释,并且牢记相应的忠告:如果确保从一开始就只使用客户端代码中的可转换类,那么就可以避免许多问题。为了及早发现问题,只要在宿主模式(hosted mode)下运行,就要对照JRE仿真库检验你的代码。因而,第一次运行应用时,就会发现大部分不支持的库。所以,要及早并且经常运行。

  现在,笔者给出的新忠告就是“分而治之”,具体意思就是一开始就把应用代码分成三个不同的部分:客户端代码、RPC相关代码和服务器端代码,然后构建相应的Eclipse项目,从而完成任务。这样一来,就可以利用不同的Java语言版本,用于客户端和服务器部分。笔者用Java 5构建了应用的服务器部分(服务器小程序代码);但如果使用Mustang版本,那么在本文的代码片段中(由于篇幅有限,本文所涉及的程序代码可通过以下链接查询:),可以用Java 6取代Java 5。即便在服务器端仍然使用J2SE 1.4.2,这种分治法也可以在将来提供更大的灵活性,明确分离代码(“分离问题”),而不会在GWT宿主模式下限制调试操作。如果所有部分都在一个Eclipse项目中,则需要非常严谨,特别是在服务器端上。不然,就会出现编译或者运行问题。

  需要使用特殊的命名约定,这样可以清楚确认不同项目,并且简化部署脚本。可以使用譬如名为GWT-<ModuleName>的Eclipse工作集来包括所有三个项目。这里,“ModuleName”是识别Web应用的GWT模块的名称;在笔者的环境中,它名为“JUnit2MR”,因为该Web应用通过IBM Rational的新Java teamapi,把JUnit错误消息连接到ClearQuest修改请求(MR)上。使用工作集,所需的一切都紧紧连在一起。

  • 客户端代码:包含与用户界面相关的代码,可以转换成JavaScript。因此,局限于J2SE 1.4.2和GWT运行时支持。启用每个项目的Eclipse Java编译器设置和“Java编译器错误/警告”,把Java依从级别调整到1.4、把源代码和类文件兼容性调整到1.4(假设不是使用1.4之前的JDK版本)。该项目的名称是<ModuleName>-client,譬如“JUnit2MR-client”,它依赖于构建路径设置中的<ModuleName>-rpc项目。程序包名称类似<com.company.project>.gwt.<moduleName>.client。
  • RPC相关代码:包含RPC相关的代码,可以转换成JavaScript。该项目遵从与上述客户端代码项目同样的指导准则。项目名是<ModuleName>-rpc,譬如“JUnit2MR-rpc”,它并不依赖于其他任何项目。程序包名称与<ModuleName>-client项目的程序包名称一样。RPC项目包含客户端上的远程接口、RPC期间由GWT进行序列化的数据传输对象,以及全局常量类。
  • 服务器端代码:含有服务器小程序代码,如果服务器端由Java服务器小程序组成。如果使用Tomcat 5.5或者Tomcat 6,可以充分利用Java 5+的全部功能。启用每个项目的Eclipse编译器设置,然后使用Java 5编译器设置,依从级别设置为5.0。如果使用Eclipse 3.2.2,那么其新的“源代码→清理”特性也值得配置。该项目名称是<ModuleName>-server,譬如“JUnit2MR-server”,它依赖于构建路径设置中的<ModuleName>-rpc项目。如果按照GWT的默认程序包提案进行编程,程序包名称是<com.company.project>.gwt.<moduleName>.server。

***

NO.2 调试和错误报告不仅仅只有Window.alert ()

调试和错误报告不仅仅只有Window.alert ()
  在创建GWT应用时,其实可以使用IDE的全部调试功能。但在深入分析何处可能出现错误之前,需要代码的客户端和服务器端都有可靠的异常报告机制。使用try/catch代码块通常可以做到这一点。在客户端的catch代码块中,应当注意这一现实:默认的方法调用e.printStackTrace()不是在所有情况下都适合的解决办法。它适用于应用运行在GWT宿主模式下,把文本输出到Eclipse控制台。不过在Web模式下,要问问自己:“我发送到stdout或者stderr的堆栈跟踪信息和错误信息会在什么地方显示?”一种可能的解决方法就是使用Mat Gessel的调试实用程序类(http://www.asquare.net/gwttk),但是需要JavaScript控制台来查看Web模式下的结果。浏览器

  在客户端,建议要做的一件事就是,使用GWT.setUncaughtExceptionHandler()方法,为任何未被发现的异常提供自己的异常处理程序。发现了这几种异常后,有几个选择:GWT.log(message, caught)、Debug.println (message_with_stacktrace);如果使用Mat Gessel的Debug类,Window.alert(message_with_stacktrace),或者自己定制的错误报告。
  视来源而定,会得到“无法装入模块”或者“未被发现的异常被漏过”的信息。笔者编写了一个小小的DebugUtility类,它提供了易于定制的默认客户端错误处理机制(见代码片段1)。

  在服务器端,可以使用java.util.logging API或者log4j的广泛功能,具体取决于个人偏好或者项目的约束条件。但要是没有为GWT的com.google.gwt.user.server.rpc.RemoteServiceServlet类打补丁,对于未被发现、未被检查的异常,只会在堆栈跟踪里面得到提示,指向生成该错误的服务器端类。对于catch()代码块里面发现及报告的被检查的异常,一切都正常。
  代码片段2中显示的代码对RemoteServiceServlet类的processCall()方法起到了补充,它发挥了神奇作用(我的语句标以粗体字)。

***

NO.3 当心GWT Shell的“刷新”按钮陷阱

当心GWT Shell的“刷新”按钮陷阱
  在宿主模式下启动应用时,会在任务栏上看到“刷新”按钮。要是摁了这个按钮,GWT就会把修改过的Java客户端源

代码重新编译成Java字节码(作为.gwt.-cache/bytecode目录中的.tmp文件),然后重新装入模块。可以使用这个按钮来缩短编辑→编译→调试周期,但在使用这项特性时要牢记几个方面:浏览器

  • 只有修改过的源代码才重新编译,也就是说,不会为依赖修改过代码的文件生成新的字节码。所以,如果改变了全局常量的值,假设public final int字段的值,不会立即在相关文件看到这个变化。
  • 只有修改过的源代码才由GWT重新编译。这意味着,即便Eclipse IDE里面的“Project clean”也帮不上忙。要影响到所有的相关源代码,譬如通过添加新的空行。

  因为这个过程相当笨拙,笔者的忠告是在修改全局常量时遵循以下四个步骤:
  1.在相应的源文件里面改变public final constant值;
  2.重新编译改变后的源代码;
  3.移除整个<ModuleName>-client/.get-cache/bytecode目录,从而删除GWT缓存内容;
  4、使用Eclipse里面的“Run as”,重新开始启动应用,从而创建带重新编译后字符码的新GWT缓存内容,这种情况下,最好忽视“刷新”按钮,不过在有些情况下,删除整个<ModuleName>-client/.get-cache/bytecode目录后可以使用“刷新”按钮。
  在修改服务器端代码时,GWT字节码缓存内容不受影响。不过,嵌入的Tomcat实例会缓存它,因而你在使用“刷新”按钮后,只有重新开始启动应用后最初改变的代码才会得到认可。所以为了安全起见,改变服务器端代码后,最好还是重新开始启动应用。

***

NO.4 在宿主模式下读取Servlet Init参数。

在宿主模式下读取Servlet Init参数。
  在处理系统时,一般不希望服务器小程序源代码中有硬编码的数据库连接参数。通常会从属性文件

读取这些参数;或者更好的是,把它们作为init参数提供给服务器小程序(作为应用的Web.xml文件的一部分)。如果在Web模式下运行应用那没有什么,但在宿主模式下会出问题。这是由于GWT宿主模式下的服务器小程序处理存在限制。数据库
  好消息是,只要修改将由嵌入式Tomcat实例使用的Web.xml文件,就可以解决这个问题。为此,修改<ModuleName>-client/tomcat/webapps/ROOT/WEB-INF目录中的Web.xml文件(或者必要时创建一个):除了嵌入式Tomcat的GWTShellServlet映射外,添加带有init参数的上下文部分,如本文JUnit2MR示例的代码片段3所示。因为上下文信息是“全局性的”,而不是针对特定的服务器小程序,在这里只有一部分的init参数信息,或者使用特殊的命名方案,把参数与不同的服务器小程序联系起来。如果使用这个新的web.xml文件,可以删除src/web/WEB-INF文件夹中的那个旧文件。
  在服务器小程序代码中,访问init参数的方式与Web模式下读取它们的方式一样,譬如final String host = getInitParameter("host")。笔者实现这一点的办法就是修改GWT的RemoteServiceServlet,方法跟第二个忠告里面的如出一辙。现在,只要覆盖GenericServlet的getInitParameter()方法,以便使用getServletContext(),而不是 getServletConfig()。
  另一个忠告是,如果在宿主模式下和Web模式下测试不同的服务器代码,略过Gant脚本中的GWT编译部分、而是从“temp”位置拷贝编译前的JavaScript代码,可以节省时间。这适用于客户端代码复杂、编译时间长达10分钟的情形。

***

NO.5 在浏览器里面显示PDF文件

在里面显示PDF文件
  大多数实际的Web应用提供了生成及阅读PDF文件的方法。本文假设这个PDF文件由服务器小程序生成,譬如通过JasperReport。以后只要点击某个超文本链接,就可以在浏览器里面阅读生成的文件。如果想在宿主模式下和Web模式下测试这项特性,建议采取以下步骤:
  1.设计一个RPC接口,接受告诉服务器是在宿主模式下运行还是在Web模式下运行的布尔参数。接口方法会返回的字符串应当带有服务器小程序生成的PDF文件的名称(即文件名的最后一部分)。
  2.根据代码片段4显示的代码,实现服务器小程序代码,这取决于布尔参数“isScript”。
  3.在客户端:在窗口组件代码里面,使用GWT.isScript()参数调用createXyzPDF()方法,从而生成包含服务器小程序结果字符串的外部超文本链接。
  代码片段4显示了接口方法名为createSummaryPDF()的示例。从服务器小程序返回的字符串是“summary.pdf”。
  这当然不是处理这种情况的惟一办法,但目前适用于我们这个示例。请注意:在宿主模式下启动应用之前,必须在<ModuleName>-client project's src/…/public文件夹中至少创建一个虚假的“summary.pdf”文件(文件名从服务器小程序返回)。不然,在浏览器中点击了超文本链接后,GWT试图读取PDF文件时,会出现“HTTP 404-找不到网页”的信息。
浏览器

***

NO.6 力求获得无状态服务器

力求获得无状态服务器
  设计客户机/服务器Web应用时要考虑的一个关键问题就是:如何处理会话和状态管理?在Web 1.0时代,答案很显然:会话和状态管理是一个服务器问题。但若使用GWT,就有另一个选择。服务器再也不一定是只提供HTML内容的“web”服务。使用GWT RPC,服务器现在可以支持只提供结构化数据的服务――在本文示例中,服务由服务器小程序实现。
  那么,GWT对会话和状态管理有何影响呢?GWT的技术领导Bruce Johnson在去年的JAOO大会上指出,若使用GWT,会话管理现在应当是一个客户端问题。图1显示的幻灯片评述了种种变化。
  在本文的JUnit2MR GWT应用中,笔者一开始使用传统方法来处理服务器小程序中的会话状态。但这是相当笨拙的任务,于是寻找另一种选择。因此,看了Bruce的幻灯片后,决定重新设计整个应用。但这一步需要改变所有RPC接口、缓存策略;最重要的是,还要改变所有的服务器小程序。因此笔者的建议是:及早考虑在何处实施会话和状态管理地,不妨试试Bruce Johnson的诀窍。最终会收到成效。
  由于这个决定,客户端对象之间有了更多的联系。于是笔者使用了有名的GoF中介者模式(mediator pattern)。不过,在客户端有一些JDK 1.4和GWT运行库的限制。因此,重新实现了PropertyChangeEvent类和中介者支持,来处理监听程序注册和消息广播。

***

NO.7 使用Selenium实现GWT Web测试的自动化

使用Selenium实现GWT Web测试的自动化 
  Selenium是一种开源工具,它能够轻松测试包含丰富、互动的客户端内容的Web应用。 所以,它非常适用于测试像用GWT创建应用那样的Ajax应用。
          当然,GWT里面仍有JUnit和JUnit支持功能,特别是针对系统的异步部分。这里着重介绍Selenium,因为它易于使用(至少它的IDE是这样)、功能强大。最后但并非最不重要的一点是,它与JUnit有许多共同之处。可以使用Selenium IDE来记录GUI用例,然后使用其“Play”特性来运行记录下来的操作。每个操作之后跟着类似JUnit的“assert”命令,负责确认页面上的某些文本。该IDE是Firefox的扩展插件,但务必要使用最新版本的Selenium:Selenium IDE 0 .8 .7,因为它包含了“waitFor…”命令的重大修正版。说到测试Ajax应用,这些命令以及“pause”命令非常重要。

***

NO.8:使用Groovy Gant脚本部署应用。

         在GWT宿主模式下试运行应用,这确实很好,但把应用部署到应用服务器上或者类似Tomcat的服务器小程序容器上, GWT的真实功能才会体现出来。在这一步,需要创建一个war文件,它会自动拷贝到Tomcat“webapps”目录。当然,可以使用Ant和ant-contrib进行所有必要的准备、编译、拷贝及其他任务。但由于Ant脚本变得更复杂后, ant-contrib控制结构和属性regex处理有一点笨拙。于是可以使用集Groovy和Ant两者之所长的Gant。安装Groovy和Gant用不了10分钟。然后,就可以使用来自“build.properties”文件的普通属性,即可定制“build.gant”脚本。

http://www.bwcsc.net/?q=node/124

GWT(国际化)

功能介绍(国际化)

      项目开发过程中经常需要一些可配置的常量,例如查询最大条数,目录位置等。在传统的Java应用程序中这些内容通常会放在
属性文件中(Properties文件),但是使用属性文件有些弊端,第一,不支持类型,所有的内容都是String,第二是,只有在具体使用
的时候才能发现有些属性没有定义,而不能在编译的时候发现。
      那么GWT如何处理这个问题呢?GWT中有一个特殊的接口com.google.gwt.i18n.client.Constants可以使用这个接口达到
定义常量的效果,并且这些常量在编译的时候被绑定,而且可以支持类型。

     使用GWT主要有以下几步:

第一步,建立一个集成于Constants的接口,例如:

public interface NumberFormatConstants extends Constants {
/
   * @return the localized decimal separator
   */
String decimalSeparator();
/

   * @return the localized thousands separator
   */
String thousandsSeparator();
}

第二步,根据接口中定义的方法定义一个跟接口同名的属性文件,例如:

#NumberFormatConstants.properties
decimalSeparator = ,
thousandsSeparator = .

第三步,获取文件中定义的内容,例如:

public void useNumberFormatConstants() {
NumberFormatConstants constants = (NumberFormatConstants) GWT.create(NumberFormatConstants.class);
String decimalSep = constants.decimalSeparator();
String thousandsSep = constants.thousandsSeparator();
String msg = "Decimals are separated using '" + decimalSep + "'";
msg += ", and thousands are separated using '" + thousandsSep + "'";
showMessage(msg);
}

上述三步中在第二步和第三步中间隐含了伊特特殊的步骤,就是GWT编译器结合接口文件和属性文件编译出了一个类,这个类实现了这个接口,每一个方法返回属性文件中的值。
其中GWT.create()方法可以获得生成的中间类的引用。

通常情况下,接口方法明和属性文件中的名字相同,例如:
String decimalSeparator(); 和 thousandsSeparator = .
但是也可以自定义接口方法和属性文件中内容的映射,例如:

public interface NumberFormatConstantsWithAltKey extends Constants {
/**
   * @gwt.key fmt.sep.decimal
   * @return the localized decimal separator
   */
String decimalSeparator();
/**
   * @gwt.key fmt.sep.decimal
   * @return the localized thousands separator
   */
String thousandsSeparator();
}

@gwt.key fmt.sep.decimal 定义了属性文件中key的内容,所以属性文件应该为:
#NumberFormatConstants.properties
fmt.sep.decimal = .
fmt.sep.thousands = ,
Constants子接口中定义的方法必须满足如下形式:

T methodName()

这里T是一个返回值,T可以使用如下表中的所有类型:
T类型                        属性文件定义
String                        简单的字符串
String[]                     使用逗号分割的字符串,如果某个字符串中包含逗号需要使用\作为转移字符,例如:'\\,'
int                            int值,在编译的时候做类型检查
float                         float值,在编译的时候做类型检查
double                       double值,在编译的时候做类型检查
boolean                     boolean值"true" 或者 "false"), 在编译的时候做类型检查
Map                          使用逗号分隔的字符产,每一个字符产在属性文件中有一条定义,定义了一个Key-Value值

Map示例:
a = X
b = Y
c = Z
someMap = a, b, c

Map someMap();方法得到的内容为:{a:X, b:Y, c:Z}

ConstantsWithLookup
ConstantsWithLookup是Constants的子接口,用法一样,只不过ConstantsWithLookup有一组通过属性名字获取属性值的方法:
getBoolean(String)        通过名字找到boolean型内容
getDouble(String)         通过名字找到double型内容
getFloat(String)            通过名字找到float型内容
getInt(String)              通过名字找到int型内容
getMap(String)            通过名字找到Map型内容
getString(String)          通过名字找到String型内容
getStringArray(String)   通过名字找到String[]型内容

效率问题:Constants效率比ConstantsWithLookup高,为什么呢?Constants在编译的时候会生成对应的JavaScript代码,
GWT Compiler会根据程序中是否使用了某些属性来决定这些内容是否会被编译为JavaScript,所以及时在Constants中声明
了某些方法,如果在代码中不使用的话,不会被编译为JavaScript代码的。
但是ConstantsWithLookup有根据属性名字查找属性内容的方法,所以,GWT Compiler不能根据上述方法确定属性是否被使用,
所以所有的属性内容都回被编译为JavaScript代码。
这是ConstantsWithLookup的优点,也是缺点!

Message类

在使用Constants(或者ConstantsWithLookup)的时候,我们只能使用预定义的消息,有些时候我们需要可变的消息。
例如:
    我们需要一个通用的消息再加上一个功能名字的参数怎么实现呢?

Message类相当于Java中的Properties,ResourceBundle和MessageFormat的联合体,例如:

消息文件类:

public interface GameStatusMessages extends Messages {
/**
   * @param username the name of a player
   * @param numTurns the number of turns remaining
   * @return a message specifying the remaining turns for a player
   */
String turnsLeft(String username, int numTurns);
/**
   * @param numPoints the number of points
   * @return a message describing the current score for the current player
   */
String currentScore(int numPoints);
}

属性文件定义:
turnsLeft = Turns left for player ''{0}'': {1}
currentScore = Current score: {0}
使用:
public void beginNewGameRound(String username) {
GameStatusMessages messages = (GameStatusMessages) GWT.create(GameStatusMessages.class);
// Tell the new player how many turns he or she has left.
int turnsLeft = computeTurnsLeftForPlayer(username);
showMessage(messages.turnsLeft(username, turnsLeft));
// Tell the current player his or her score.
int currentScore = computeScore(username);
setCurrentPlayer(username);
showMessage(messages.currentScore(currentScore));
}

我们可以看到在使用的时候基本一致,但是,可以使用参数配置原有的消息。
另外Message的方法的格式为:
    String methodName(optional-params)
从中我们也可以看出区别,Message只能使用String类型的参数。

Constants(或者ConstantsWithLookup)和Message的区别是:
Constants用来定义系统的常量,支持多种类型。
Message用来定义系统的消息,可以支持参数化消息,但是只支持String类型的内容。

在使用Constants和Message的时候,可以将属性文件的编码设置为UTF-8这样,就不用
使用Native2ascii将正常的文件转移为utf-8的替换文件了。
当然如果你觉得不麻烦也可以使用传统的Java属性文件(使用native2ascii处理过得文件)。

功能介绍(JavaScript Native Interface)
JavaScript Native Interface = JSNI
JSNI定义了在GWT环境下,Java与JavaScript交互的一种方法。

虽然GWT的一些核心的方法是用JavaScript编写的,但是这里还是不推荐使用JNI,应为这样做与GWT的初衷相悖,
并且,有一定的难度,开发调试也相对困难。
Java调用JavaScript方法:
JSNI方法定义需要使用native关键字,并且需要在参数列表之后,结尾的分号之前定义。JSNI方法的开始使用/*-{
结尾使用}-*/,例如:

public static native void alert(String msg) /*-{
$wnd.alert(msg);
}-*/;

当上述方法在Java中调用的时候,实际上将会调用Window的alert方法,将传入的内容打印出来。
在Hosted Mode下,断点可以设置在上述方法中,可以方便的查看传入的参数。

JavaScript调用Java方法:
方法调用方式:
    [instance-expr.]@class-name::method-name(param-signature)(arguments)
属性访问方式:
    [instance-expr.]@class-name::field-name

[instance-expr.]
    用来区分实例方法调用还是静态方法调用。在调用实例方法的时候必须出现,在调用静态方法的时候不能出现。
class-name
    类的名字。
method-name
    方法的名字
param-signature
    方法的参数列表,这里使用的是内部形式(参考Java虚拟机Class格式),但是不需要写返回值类型。
arguments
    调用方法的实际参数。
例如:
public class JSNIExample {
String myInstanceField;
static int myStaticField;
void instanceFoo(String s) {
    // use s
}
static void staticFoo(String s) {
    // use s
}

// 该方法被调用的时候将在JavaScript中执行,并且
// 可以使用JavaScript中的内容。
public native void bar(JSNIExample x, String s) /*-{
    // 调用这个实例本身的instanceFoo方法
    this.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);
    // 调用x实例(输入参数)上的instanceFoo实例方法
    x.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);
    // 调用静态方法 staticFoo()
    @com.google.gwt.examples.JSNIExample::staticFoo(Ljava/lang/String;)(s);
    // 读取这个实例的变量
    var val = this.@com.google.gwt.examples.JSNIExample::myInstanceField;
    // 设置x上的实例变量
    x.@com.google.gwt.examples.JSNIExample::myInstanceField = val + " and stuff";
    // Read static field (no qualifier)
    @com.google.gwt.examples.JSNIExample::myStaticField = val + " and stuff";
}-*/;
}

Java和JavaScript之间参数的传递:
Java -> JavaScript
Java type                              JavaScript Type
numeric primitive                     a JavaScript numeric value, as in var x = 42;
String                                   a JavaScript string, as in var s = "my string";
boolean                                 a JavaScript boolean value, as in var b = true;
JavaScriptObject (see notes)    a JavaScriptObject that must have originated from JavaScript code, typically as the return value of some other JSNI method
Java array            an opaque value that can only be passed back into Java code
any other Java Object        an opaque value accessible through special syntax

异常
调用JSNI方法的时候会抛出一个JavaScriptException的异常,但是由于JavaScript不是一个强类型的语言,所以
无法想Java一样处理JavaScript异常。一个好的方式是在Java中处理Java异常,在JavaScript中处理JavaScript异常。
另外在JSNI方法,Java普通方法混掉的过程中,异常可以从最底层移植抛到最想的调用层,例如:
1. Java method foo() calls JSNI method bar()
2. JavaScript method bar() calls Java method baz()
3. Java method baz() throws an exception
baz()中抛出的异常可以蔓延到bar方法,可以在foo方法中捕获。

从Host Model到 Web Model

在Host Model方式下,GWT并不将Java代码编译为JavaScript,而是在GWT环境中直接运行Java bytecode,
但是项目正式部署之后使用的是Web Model,那么如何从Host Model迁移到Web Model呢?

首先需要将Java代码编译为JavaScript代码。
使用如下命令可以将Java代码编译为JavaScript代码:
java -cp "%~dp0\src;%~dp0\bin;%~dp0\../../gwt-user.jar;%~dp0\../../gwt-dev-windows.jar" com.google.gwt.dev.GWTCompiler -out "%~dp0\www" %* com.google.gwt.sample.hello.Hello

-cp 指定源代码目录,Class目录,和GWT的jar文件的路径
-out 指定JavaScript代码的输出路径
com.google.gwt.sample.hello.Hello 指定编译的Module,一般是gwt.xml文件中entry-point类去掉client之后的内容。

当代码量比较大的时候,需要指定Java使用内存的大小,否则会内存溢出。
java -Xmx512m -Xms128m -cp "%~dp0\src;%~dp0\bin;%~dp0\../../gwt-user.jar;%~dp0\../../gwt-dev-windows.jar" com.google.gwt.dev.GWTCompiler -out "%~dp0\www" %* com.google.gwt.sample.hello.Hello

之后将编译成的JavaScript代码拷贝到Web项目的根目录中,与WEB-INF相同层次的目录。

最后需要将gwt.xml文件中定义的service编程对应的Servlet。

=>

     Calendar
     com.google.gwt.sample.dynatable.server.SchoolCalendarServiceImpl

     Calendar
     /calendar

使用数据源

Hosted Mode 虽然开发起来很方便,但是也有缺点,例如,数据源的配置就有问题。
在GWT Hosted Mode下无法配置数据源,一种可选的方式是使用一个假的数据库链接
管理类,这个类的接口返回Connection,内部以DriverManager的方式实现,等待
后续部署之后再切换到数据源模式。

日志处理(Log4J)

回想GWT应用程序,client包内部的代码将会被编译为客户端JavaScript代码,所以这里
不需要记录日志,也不可能使用Log4j。
但是Server包内的内容在服务器上运行,需要合理的使用日志。

一个简单的Login示例

代码结构如下:

└─src
    └─com
        └─jpleasure
            └─gwt
                └─logon
                    │ LogonDemo.gwt.xml                       GWT配置模块文件
                    │
                    ├─client                                          客户端代码包
                    │ │ LogonDemo.java                        GWT代码的入口点
                    │ │ LogonDemoController.java           画面迁移控制类
                    │ │
                    │ ├─exception                                 异常定义包
                    │ │      ApplicationException.java        应用程序异常
                    │ │     
                    │ ├─panel                                       页面Panel包

http://www.bwcsc.net/?q=node/192

GWT创建可重用的模块(Module)

构建一个GWT应用程序,就相当于构建了一个可重用的模块。唯一的区别在于,我们的应用程序已经指定了入口点,因而已经成为一个能够被独立加载到GWT的主机模式浏览器,或者Web浏览器中的GWT应用程序。要重用应用程序的组件,可以在另一个应用程序中引用该应用程序的模块文件。

创建模块的方式与创建应用程序相同,即都要使用GWT的applicationCreator脚本。而且,考虑到要使用Ant自动将模块打包为jar文件以便于部署,可以在使用该脚本时指定ant参数,从而构建一个ant文件。
GWT的模块结构是分层的继承结构。例如,如果编写的模块继承了GWT的User模块,那么使用你的模块的模块或应用程序也会自动继承GWT的User模块。这个特性很重要,因为它确保了你的模块用户能够自动获得运行该模块的全部必要条件。而且,GWT在此基础上又向前迈进了一步,它还允许开发者向模块中注入资源,从而保证CSS或其他JavaScript库能够被自动包含。

例如,要创建一个部件模块,该模块需要包含默认的CSS文件,那么就可以在XML模块文件中包含相应的CSS:
<module>
<inherits name='com.google.gwt.user.User'/>
<stylesheet src="widgets.css"/>
</module>

以上CSS文件应该保存在模块的public目录中,这样当其他模块继承该模块时,就会自动获得widgets.css文件而无需直接包含它。
类似地,也可以在模块中使用script标签来包含JavaScript文件,例如:
<module>
<inherits name='com.google.gwt.user.User'/>
<!— Include google maps —>
<script src="http://maps.google.com/
maps?file=api&amp;v=2&amp;key=ABQIAAAACeDba0As0X6mwbIbUYWv-
RTbvLQlFZmc2N8bgWI8YDPp5FEVBQUnvmfInJbOoyS2v-
qkssc36Z5MA"></script>
</module>

这里的script标签与在HTML文件中用于包含JavaScript库的script标签作用相似,只不过在这里指定的文件将被包含该模块的每个模块自动包含。

从GWT 1.4开始,开发者可以使用图像捆绑在可重用的模块中包含资源。比如,通过图像捆绑可以把很多图像打包成一幅图像进行部署。如果你在模块中使用了图像捆绑,那么使用该模块的应用程序也将自动生成单幅图像。在第6章中,使用图像捆绑构建了Gadget Desktop应用程序中的配件工具栏。

gwt学习—新增包

在一个gwt(google web toolkit)中,一般有一个默认的包“client”,如果我新增了一个包“lib”应该怎么做呢?
    方法一(直接增加资源路径):
       比如你的项目名为“AX”,那么会有一个对应的文件“AX.gwt.xml”,在此文件中加入如下内容
       <module>
          。。。。。
           <source path="lib"/>
          。。。。。
        </module>
    方法二(多重包含):
       首先找到文件“AX.gwt.xml”在文件中加入对另外一个文件(比如“YY.gwt.xml”)的引用,如下
       。。。。
       <inherits name='com.KF.YY'></inherits>
       。。。。
       然后在文件“YY.gwt.xml”中加入与方法一相似的代码,如下
       <module>
           <source path="lib"/>
        </module>
    方法三(新增包与包“client”不在同一目录时)(参考)
       这里举一个定义组件的例子
       自定义组件包为“com.gwt.components.client”
       在增加文件:在“com.gwt.components”下增加文件“user.gwt.xml”
          内容:
<module>
  <inherits name="com.google.gwt.core.Core"/>
</module>
       在AX.gwt.xml中加入如下
       。。。。
       <inherits name='com.gwt.components.user'></inherits>
       。。。。
       此时默认的会包含文件“user.gwt.xml”所在目录下的“client”,如果在此目录下你还有其它的目录,就需要用文件“user.gwt.xml”中加入“<source path='你的目录'”。
在google的网站上有对“module”更专业的说明:http://code.google.com/webtoolkit/documentation/com.google.gwt.doc.DeveloperGuide.Fundamentals.Modules.html

gwt在eclipse、tomcat中单步调试客户与服务的完整实例

使用tomcat进行浏览,而不使用gwt自带的,这样可以取得tomcat的上下文信息,并可以单步客户端
完整的源代码(为了方便编译后的文件也保留了):Wylpeace-tomcat
环境的建立
1,下载eclipse3.3 :http://download.eclipse.org/eclipse/downloads/index.php
2,下载tomcat6.0 : http://tomcat.apache.org/download-60.cgi
3,下载Tomcat Plugin:http://www.sysdeo.com/eclipse/tomcatplugin
4,安装(怎么安装就自己去google吧)
正式开工
建一个用eclipse开发的gwt项目(如果不想建立,可以下载源代码)
    gwt的命令行方式:http://code.google.com/webtoolkit/gettingstarted.html
   
在eclipse中配置tomcat的运行环境
   
    首先把插件配置好
    window》preference》tomcat:配置“tomcat home”与“tomcat vesion” 》advanced :配置“tomcat base”(与“tomcat home”相同) —》jvm setting : 选择“jre”,注意一定是jdk下的jre,不然不能调试服务端代码
   
    然后呢(配置的内容比较多,没有办法)
    1,在项目下建,如下目录wylpeace.samples.select.WylpeaceSelect/WEB-INF/classes、wylpeace.samples.select.WylpeaceSelect/WEB-INF/lib
    2,在“WEB-INF”下加入文件“web.xml”内容如源代码
    3,把文件gwt-user.jar、gwt-dev-windows.jar、gwt-ll.dll、swt-win32-3235.dll、ojdbc14.jar放入lib目录。
    4,增加库引用“gwt-user.jar、gwt-dev-windows.jar、ojdbc14.jar ”
    5,修改class文件的输出路径,如下图
配置gwt的hosted
   
    1,右键项目“Wylpeace”
》debug as—》debug如下图所示
    注意图中的选中部分“-noserver -port 8080”(用这种方式可以改变gwt的默认端口8888),
    还是在这个图中“classpath”标签进行修改,参见:http://www.blogjava.net/peacess/archive/2007/07/24/49447.html
配置tomcat插件
所有配置都完成了,我们可以开始单步调试了
首先重起tomcat(在eclipse中),在调试运行“wylpeace”
这时会提示找不到网页,修改url为:http://localhost:8080/wylpeace.samples.select.WylpeaceSelect/WylpeaceSelect.html
好了整个过程结束,如果有什么问题请联系
    mail:moc.361|ssecaep#moc.361|ssecaep
    qq:64407724
你也可以参考:http://jroller.com/page/masini?entry=deploy_and_debug_google_web
你也可以参考:http://groups.google.com/group/Google-Web-Toolkit/browse_thread/thread/338c4b765d7dfc39/c9382d0e65266248?q=tomcat&rnum=3#c9382d0e65266248

GWT RPC收藏

GWT可以将JAVA代码转换成Javascript代码,让不懂js的开发人员也能编写具有丰富客户端交互功能的web程序。但大部分程序还是要和服务器进行交互的,那么GWT是如何实现客户端与服务端交互的呢。
GWT有两种与服务端进行交互的技术。GWT RPC和基于普通AJAX的远程交互技术。这里主要介绍下GWT RPC技术。
首先,我们创建一个类来表示客户端和服务端要交互的数据类型。在GWT RPC中,如果要交互的数据是一个复杂类型的话,那么这个类要实现IsSerializable的接口,来使该类能被正常的序列话和反序列化。代码如下图所示。

package cn.edu.zju.cs.client;
import com.google.gwt.user.client.rpc.IsSerializable;
public class Person implements IsSerializable{
    private String name;
    private int age;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
接着,我们要创建一个接口,此接口要继承RemoteService接口,并添加一些方法。

package cn.edu.zju.cs.client;
import com.google.gwt.user.client.rpc.RemoteService;
public interface MyService extends RemoteService{
    public Person getPerson(String name, int age);
}
接下来,创建一个该接口对应的异步接口,异步接口的方法返回值均为void,并且其对应的每个方法都比原接口多一个类型为AsyncCallback的参数。

package cn.edu.zju.cs.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface MyServiceAsync {
    public void getPerson(String name, int age, AsyncCallback callback);
}
然后,我们在对应的server目录下面创建一个该接口的实现类,这个实现类要继承RemoteServiceServlet类

package cn.edu.zju.cs.server;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import cn.edu.zju.cs.client.MyService;
import cn.edu.zju.cs.client.Person;
public class MyServiceImpl extends RemoteServiceServlet implements MyService {
    public Person getPerson(String name, int age) {
        // TODO Auto-generated method stub
        Person person = new Person();
        person.setName(name);
        person.setAge(age);
        return person;
    }
}
   
接下来,我们在模块配置文件Zhen.gwt.xml中进行相应的修改,添加<servlet path="/person" class="cn.edu.zju.cs.server.MyServiceImpl"/>,使GWT Shell的内置Tomcat Server能找到实现类。
到这里,所有的准备工作都做完了,我们开始在客户端调用这个远程的服务。这里要注意的是不要使用硬编码的URL,因为测试时的URL和部署时的URL是不同的。

        MyServiceAsync service = (MyServiceAsync)GWT.create(MyService.class);
        ServiceDefTarget endpoint = (ServiceDefTarget)service;
        endpoint.setServiceEntryPoint(GWT.getModuleBaseURL() + "/person");
        endpoint.setServiceEntryPoint("http://localhost:8889/cn.edu.zju.cs.Zhen/person");
        AsyncCallback callback = new AsyncCallback()
        {
            public void onSuccess(Object result)
            {
                Person person = (Person)result;
                Window.alert(person.getName());
            }
            public void onFailure(Throwable cause)
            {}
        };
        service.getPerson("zhen ye", 24, callback);
好了,到这里我们就完成了一个基本的GWT RPC的调用。假如我们要使用外部的server来代替GWT内置的Tomcat Server,我们只要在web.xml里让客户端使用的URL对应到MyServiceImpl类即可。
我们可以用eclipse的TCP/IP Monitor将请求拦截,然后看下客户端和服务端的请求数据,我们发现在HTTP请求数据中包括了接口类的名字,请求的方法,传递的参数等等数据,服务端在有了这些数据后就能将请求定位到特定的方法并进行处理。服务端的返回数据则包括了请求数据类序列后的数据。如下所示:
OK[2,24,1,["cn.edu.zju.cs.client.Person/2086786979","zhen\x20ye"],0,4]

http://www.bwcsc.net/?q=node/203

使用一个servlet来分配所有的gwt service

GWT中默认一个service对应一个servlet,这样会使web.xml要配置很多servlet, 不是很方便。我这里通过一个Dispatch servlet来分配service, 这样每个service都成了pojo,使得配置更加方便,也容易测试。 
1. Server code 
Configure web.xml like 
       <servlet> 
                <servlet-name>gwt-dispatch</servlet-name> 
                <servlet-class> 
                        com.xxxxx.gwt.GwtRpcServiceDispatch 
                </servlet-class> 
                <init-param> 
                        <param-name>prefix</param-name> 
                        <param-value>/gsvc</param-value> 
                </init-param> 
        </servlet> 
        <servlet-mapping> 
                <servlet-name>gwt-dispatch</servlet-name> 
                <url-pattern>/gsvc/*</url-pattern> 
        </servlet-mapping> 
prefix参数表示service访问url的根路径 
Why it works: Let's see the code of GwtRpcServiceDispatch.java 
   1 / 
   2  * @author xwang 
   3  * Dispatch the gwt services 
   4  */ 
   5 public class GwtRpcServiceDispatch extends RemoteServiceServlet { 
   6     private static final long serialVersionUID = -2797202947611240618L; 
   7 
   8     private Injector injector; 
   9 
  10     private int beginIndexOfClassName; 
  11 
  12     @Override 
  13     public void init(ServletConfig config) throws ServletException { 
  14         super.init(config); 
  15         // get guice injector, it init by ServletContextListener 
  16         injector = InjectorHolder.getInjector(); 
  17         if (injector == null) { 
  18             throw new UnavailableException("Guice Injector not found (did you forget to register a " 
  19                     + this.getClass().getSimpleName() + "?)"); 
  20         } 
  21 
  22         // set prefix of service url 
  23         String prefix = config.getInitParameter("prefix"); 
  24         if (prefix == null || prefix.length() == 0) { 
  25             beginIndexOfClassName = 1; 
  26         } else if (prefix.startsWith("/")) { 
  27             beginIndexOfClassName = prefix.length() + 1; 
  28         } else { 
  29             beginIndexOfClassName = prefix.length() + 2; 
  30         } 
  31     } 
这里先要设置Guice Injector, 应为我们的service中用到了guice. 其次设置prefix的长度. 
接着就可以根据URL来实例化RomteService,如下: 
   1 /
 
   2      * get service implement class's instance 
   3      * @return service implement class's instance 
   4      */ 
   5     protected Object getCurrentHandler() { 
   6         String classPath = getThreadLocalRequest().getRequestURI(); 
   7         // /gsvc/com/xxxxx/bus/domain/service/LandmarkService => com.xxxxx.bus.domain.service.LandmarkService 
   8         String className = classPath.substring(beginIndexOfClassName); 
   9         if (className.endsWith("/")) { 
  10             className = className.substring(0, className.length() - 1); 
  11         } 
  12         className = className.replaceAll("/", "."); 
  13 
  14         Class<?> clazz = null; 
  15         try { 
  16             clazz = Class.forName(className); 
  17         } catch (ClassNotFoundException e) { 
  18             e.printStackTrace(); 
  19             throw new IllegalStateException(e.getMessage()); 
  20         } 
  21 
  22         return injector.getInstance(clazz); 
  23     } 
当然我们需要重新定义RemoteServiceServlet的主处理方法: 
   1     // this is the main method of RemoteServiceServlet to process request 
   2     @Override 
   3     public String processCall(String payload) throws SerializationException { 
   4         Object serviceHandler = getCurrentHandler(); get instance of RemoteService 
   5 
   6         try { 
   7             RPCRequest rpcRequest = RPC.decodeRequest(payload, serviceHandler.getClass(), this); 
   8             return RPC.invokeAndEncodeResponse(serviceHandler, rpcRequest.getMethod(), rpcRequest.getParameters(), rpcRequest 
   9                     .getSerializationPolicy()); 
  10         } catch (IncompatibleRemoteServiceException ex) { 
  11             getServletContext().log("An IncompatibleRemoteServiceException was thrown while processing this call.", ex); 
  12             return RPC.encodeResponseForFailure(null, ex); 
  13         } 
  14     } 
还要重新定义support方法: 
   1     
RemoteServiceServlet use this judge the instance 
   2     public boolean supports(Object handler) { 
   3         return handler instanceof RemoteService; 
   4     } 
现在我们的RemoteService就成为了一个pojo如: 
   1 public interface LandmarkService extends RemoteService { 
   2 ……….. 
   3 } 
   4 
   5 public class LandmarkServiceImpl implements LandmarkService { 
   6 ……….. 
   7 } 
2. Client code 
gwt client这边需要用 
   1 ServiceHelper.registerServiceEntryPoint(instance); 
获取访问URL. 
why it works: 
   1      /** 
   2      * register service url 
   3      * @param svcObj service object 
   4      * com.xxxxx.bus.domain.service.LandmarkService => /gsvc/com/xxxxx/bus/domain/service/LandmarkService 
   5      */ 
   6     public static void registerServiceEntryPoint(Object svcObj) { 
   7         ServiceDefTarget endpoint = (ServiceDefTarget) svcObj; 
   8         String endpointText = GWT.getTypeName(svcObj); 
   9         endpointText = endpointText.substring(0, endpointText.indexOf("_Proxy")); 
  10         endpointText = endpointText.replace('.', '/'); 
  11         endpoint.setServiceEntryPoint("/gsvc/" + endpointText); 
  12     } 
GWT.getTypeName can get the class name of service object 
你可以使用如下代码的方式构造你的service 
   1 public interface LandmarkService extends RemoteService { 
   2     public static class Initer { 
   3         private static LandmarkServiceAsync instance; 
   4         public static LandmarkServiceAsync getInstance(){ 
   5             if (instance == null) { 
   6                 instance = (LandmarkServiceAsync) GWT.create(LandmarkService.class); 
   7                 ServiceHelper.registerServiceEntryPoint(instance); 
   8             } 
   9             
  10             return instance; 
  11         } 
  12     } 
  13 …….. 

http://www.bwcsc.net/?q=node/208

Gwt延迟绑定介绍 

Gwt延迟绑定介绍 
Gwt的延迟绑定是一种gwt对反射机制的一种支持方案。简单的说就是在使用gwt进行代码开发的时候,可以使用接口和抽象类,而不用管它的实现,在编译后或者host模式的情况下,gwt会自己跟模块配置的信息来使用具体哪一个实现类替代代码中的接口和抽象类。 

使用说明: 

1. 定义接口或者抽象类,然后再定义一个实现类。 
2. 在xxx.gwt.xml(模块文件中定义)替换的参数信息和具体替换的类 

Xml代码 

<replace-with class="com.google.gwt.user.client.impl.DOMImplMozilla">
                <when-type-is class="com.google.gwt.user.client.impl.DOMImpl"/>
                <when-property-is name="user.agent" value="gecko1_8"/>
        </replace-with>
上面的配置是将在firefox浏览器里面使用DOMImpl该类的地方使用DOMImplMozilla这个类进行替换。其中的replace-with是指实际用到的类,when-type-is则是要替换的类,when-property-is则是一些参数信息,可以添加0到多个。另外,关于参数的配置还可以加一些逻辑的限制,如Any, 

Xml代码 
<any>
      <when-property-is name="user.agent" value="gecko"/>
      <when-property-is name="user.agent" value="gecko1_8" />
    </any>

3. 在代码中使用 
通过GWT.create方法可以动态的获取不同的实现类,如下: 
DOMImpl impl = (DOMImpl) GWT.create(DOMImpl.class); 
再结合gwt的dom模块的配置信息(如下),impl对象将会根据不同的浏览器而动态采用不同的domimpl类的实现。 

Xml代码 
<module>
        <inherits name="com.google.gwt.core.Core"/>
        <inherits name="com.google.gwt.user.UserAgent"/>

        <replace-with class="com.google.gwt.user.client.impl.DOMImplOpera">
                <when-type-is class="com.google.gwt.user.client.impl.DOMImpl"/>
                <when-property-is name="user.agent" value="opera"/>
        </replace-with>

        <replace-with class="com.google.gwt.user.client.impl.DOMImplSafari">
                <when-type-is class="com.google.gwt.user.client.impl.DOMImpl"/>
                <when-property-is name="user.agent" value="safari"/>
        </replace-with>

        <replace-with class="com.google.gwt.user.client.impl.DOMImplIE6">
                <when-type-is class="com.google.gwt.user.client.impl.DOMImpl"/>
                <when-property-is name="user.agent" value="ie6"/>
        </replace-with>

        <replace-with class="com.google.gwt.user.client.impl.DOMImplMozilla">
                <when-type-is class="com.google.gwt.user.client.impl.DOMImpl"/>
                <when-property-is name="user.agent" value="gecko1_8"/>
        </replace-with>

        <replace-with class="com.google.gwt.user.client.impl.DOMImplMozillaOld">
                <when-type-is class="com.google.gwt.user.client.impl.DOMImpl"/>
                <when-property-is name="user.agent" value="gecko"/>
        </replace-with>
</module>

参考: 
http://code.google.com/docreader/#p=google-web-toolkit-doc-1-5&s=google-web-toolkit-doc-1-5&t=DevGuideDeferredBinding

最近正好项目用到延迟绑定,以下为“在代码中使用”的部分 
延迟绑定很强大,反射,注解都可以支持 
我用个实际例子来介绍:例如现在GWT中有很多Animal接口的实现类,比如Cat,Dog等等,现在需要在onModuleLoad的时候能够将Animal的所有实现类实例化,简单的写法就是一个一个地new,当实现类很多时候不仅累而且容易出错,这时候可以用延迟绑定来实现 

首先在入口类里面写个空的类,然后用GWT.create方法来实例化它 

Java代码 
private static class AnimalCreator{}

public void onModuleLoad(){
  GWT.create(AnimalCreator.class);
}在配置XML文件里面做如下配置 

Xml代码 
<generate-with class="com.google.generator.AnimalGenerator">
  <when-type-assignable class="com.google.client.AnimalCreator"/>  
</generate-with>解释一下这个配置: 
当代码执行到GWT.create(AnimalCreator.class)时,GWT会调用AnimalGenerator这个类的generate方法(AnimalGenerator必须实现com.google.gwt.core.ext.Generator接口),generate方法返回一个类名的字符串(含package name),GWT.create方法将会根据这个字符串找到实际的类,然后将它实例化。 
需要指出的是,上述过程都是在compile期间发生。也就是说这个Generator的实现类并不会被编译成js,所以不要把这个类放到client包下面;同时,这也意味着在这个Generator类里面,我们可以使用jdk提供的类,而不是局限于GWT JRE Emulation 

下面直接给出AnimalGenerator的代码 

Java代码 
public class ShowcaseGenerator extends Generator {
        private String className=null;
        private String packageName=null;
        
  public String generate(TreeLogger logger, GeneratorContext context,String typeName) throws UnableToCompleteException {
    try{
            JClassType classType=context.getTypeOracle().getType(typeName);
            取包名
            packageName=classType.getPackage().getName();
            
取类名,然后加上Impl,也就是AnimalCreatorImpl
            className=classType.getClass().getSimpleSourceName()+"Impl";
            generateClass(logger,context);
          }catch(Exception e){
                  logger.log(TreeLogger.ERROR, "ERROR!!!", e); 
          }
          return packageName+"."+className;
  }
  
  //尝试创建AnimalCreatorImpl这个类
  private void generateClass(TreeLogger logger, GeneratorContext context){
                PrintWriter printWriter = null;
                
                printWriter = context.tryCreate(logger, packageName, className);
                if (printWriter == null) return; 

                ClassSourceFileComposerFactory composer = null; 
                composer = new ClassSourceFileComposerFactory(packageName, className); 
                SourceWriter sourceWriter = null; 
                sourceWriter = composer.createSourceWriter(context, printWriter); 
                //手工写入代码 
                sourceWriter.println("public "+className+" {");
                sourceWriter.indent(); 
                try{
                        JClassType superType=context.getTypeOracle().getType("com.google.client.Animal");
                        JClassType[] Types=superType.getSubtypes();
                        for(JClassType type:types){
                                sourceWriter.println("new "+type.getPackage().getName()+"."+type.getName()+"();");
                        }
                }catch(NotFoundException e){
                        e.printStackTrace();        
                }
                sourceWriter.outdent(); 
                sourceWriter.println("}");                 
                
                sourceWriter.outdent(); 
                sourceWriter.println("}");                 
                //提交写入的新类
                context.commit(logger, printWriter); 
        }
}

generate方法返回的是com.google.client.AnimalCreatorImpl,但是我们目前并没有这个类,所以执行generateClass方法来动态生成这个类,在方法里面看到,我们首先找到com.google.client.Animal的所有实现类,然后将这些类名都以new Cat();这样的形式写入类的构造函数 

那么当我们执行GWT.create(AnimalCreator.class)时,GWT会实例化我们用代码生成的AnimalCreatorImpl类,这个类的构造函数里面写着new Cat();new Dog();等等的代码,如果我们新建一个Animal接口的实现类,不用改动任何代码,这个类就会在onModuleLoad时被实例化 

仔细查看GWT的com.google.gwt.core.ext包下面的API,我们可以发现更多对反射,注解等高级特性支持的方法

http://www.bwcsc.net/?q=node/209

GWT 2.0即将发布新特性提前预览

Google Web Toolkit (GWT)是一个开源的工具集,可以让Web开发人员用Java语言创建和维护复杂的JavaScript前端应用程序。它发布在Apache License version 2.0下。GWT的目标是:使开发人员可以使用现有的Java工具来在任何浏览器上构建AJAX,从而从根本上改善用户使用网络的体验。

谷歌今年晚些时候将推出GWT 2.0。虽然GWT的开发进程比较慢,然而这几年来一直稳步提升。让我们回眸GTW走过的岁月,并展望下它的未来:

  先看看它的历史
  * GWT 1.0 :2006,5月17日
  * GWT 1.1:2006年8月11日
  * GWT 1.2 :2006年11月16日
  * GWT 1.3:2007年2月5日
  * GWT 1.4:2007年8月28日
  * GWT 1.5:2008年9月27日
  * GWT 1.6:2009年4月7日
  * GWT 1.7:2009年7月13日

GWT 2.0的发布计划是在今年晚些时候,GWT 2.0将包含巨大的改进,包括动态脚本载入、一个新的编译器优化的目录,并以新的方式使用本地浏览器来支持宿主模式(hosted mode)调试。

  宿主模式(hosted mode)是高效开发的一个关键因素,而且它有助于调试,编辑等。问题是宿主模式的浏览器如果很很特殊比如在Linux上、宿主浏览器是一个老版的 Mozilla,就难以与其他技术(如Flash)交互,而且不可能在非开发操作系统(如Mac上的IE浏览器)上调试浏览器。GWT使大多数本地浏览器 支持宿主模式,解决了这些问题。

  另一个巨大的性能提升是编译器功能的增强,配备了新的工具包。新版本的GWT承诺将会提供更快速的编译速度。
  另外一个新的特性是开发者可以自定义代码分割。通常的编译脚本代码,其大小是一个麻烦事,因为它拥有所有的代码,并且能够增长到很大。因此,初始下 载就会很慢、脚本解析时UI悬挂等。GWT 2.0提供了一个解决方案使用runasync进行代码分割。编译器决定如何组织代码,而且确保是正确的。

  另一个新特性是ClientBundle,它能够将ImageBundle扩展到任意资源类型。结合ClientBundle不仅图像捆绑在一起, 所有的资源(如CSS )都可以捆绑。最明显的好处是,只有一个文件实必须要下载的,因此,减少了HTTP请求,下载速度也更快。此外,CSS等资源的规模得到优化。在谷歌的I / O演讲中还提到,在下一个版本中, ClientBundle也许还将使用Base64来处理二进制资源。

  在GWT2.0中,有可能会创造RPC黑名单,这个名单告诉RPC子系统跳过那些你认为不会使用也不需要编译的类型。GWT2.0还承诺提供更快、 更方便和更可预见的布局。标准模式提供了新的功能,包括基于约束(constraint-based)的布局。在GWT2.0中还将有一个更新的控制面 板。

  2.0版本将GWT向前带动了一大步,为开发者和用户提高了性能。虽然不是官方的,但2.0也许还带来了新的基于XML的标记语言来定义布局。我的猜测是,语法将接近HTML 。希望新的版本能够吸引更多的GWT开发者,并希望有更多的部件库和扩展。

http://www.bwcsc.net/?q=node/820


本页面的文字允许在知识共享 署名-相同方式共享 3.0协议和GNU自由文档许可证下修改和再使用,仅有一个特殊要求,请用链接方式注明文章引用出处及作者。请协助维护作者合法权益。


系列文章

文章列表

  • Java Google Web Toolkit 信息汇总

这篇文章对你有帮助吗,投个票吧?

rating: 0+x

留下你的评论

Add a New Comment
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License