GWT开发教程

battyxing

贡献于2011-09-18

字数:31528 关键词: GWT JavaScript框架 方案 HTTP Basic

第一章 编码基础 在eclipse中添加GWT插件。升级安装源如下: Eclipse 3.5 (Galileo) http://dl.google.com/eclipse/plugin/3.5 Eclipse 3.4 (Ganymede) http://dl.google.com/eclipse/plugin/3.4 Eclipse 3.3 (Europa) http://dl.google.com/eclipse/plugin/3.3 第一节 客户端代码 1.1.1 创建入口点 新建GWT工程之后将生成的类文件都删除。并删除web.xml中如下的部分: greetServlet com.example.smallgwt.server.GreetingServiceImpl greetServlet /smallgwt/greet 然后观察生成的模块配置文件 每一个模块都有一个入口点。这个模块的入口点就是“Smallgwt”类。现在使用eclipse在com.example.smallgwt.client中创建这个类Smallgwt。同时指定接口为com.google.gwt.core.client.EntryPoint。 自动生成的代码如下: package com.example.smallgwt.client; import com.google.gwt.core.client.EntryPoint; public class Smallgwt implements EntryPoint { @Override public void onModuleLoad() { // TODO Auto-generated method stub } } 入口点类 入口点类是整个GWT应用程序的入口,也就是说,GWT应用程序运行时会首先调用这段代码,工程的入口点类在工程模块的标签中指定。在入口点类中必须实现入口点方法onModuleLoad(),这个入口点方法是整个GWT应用程序开始执行的地方。 这个方法中主要做的事情是: l 创建可视组件 l 设置事件处理句柄 l 将可视组件添加到网页上 package com.example.smallgwt.client; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.RootPanel; public class Smallgwt implements EntryPoint { @Override public void onModuleLoad() { // TODO Auto-generated method stub //创建一个按钮,按钮标题是"Click me".创建一个事件接收类,当用户点按钮的时候,这个类的方法被调用 final Button b = new Button("Click me", new ClickHandler() { @Override public void onClick(ClickEvent event) { // TODO Auto-generated method stub //弹出警告窗口,显示"Hello, AJAX Window.alert("Hello, AJAX"); } }); //将这个按钮添加到网页上。 RootPanel.get().add(b); GWT.log("Hello World!", null); } } RootPanel.get([“soltName”]).add(widget)。 如果get中为空。则得到整个网页,将组件添加到网页上。如果不为空。则将组件添加到get中的名字指定的位置。 1.1.2 HTML页面 HTML页面和普通的HTML页面没有什么区别,可以使用一切HTML标签。有几个特别的地方如下: 1、这个HTML中必须引入下面的JavaScript代码,用来加载GWT工程模块,之后GWT读入模块文件(*****.gwt.xml)来查找入口点(EntryPoint)类,整个GWT程序才得以运行。另外,所有GWT的标签都应该出现在这段JavaScript代码之前。 下面是在模块中没有定义模块别名的时候使用完整的长名字的情形: 这个nocache.js文件位于编译后的www目录下,并且其命名规则为: 模块的全路径名.nocache.js 2、在HTML的中可以加入下面这个iframe标签,其作用是为GWT提供历史支持,但这个标签是可选的。 这里的id属性值必须为“__gwt_historyFrame”。 3、如果在MyApplication.java的RootPanel中指定了使用某些id,比如这里的“solt1”和“slot2”,那么在HTML页面中也必须明确指定以这些id命名的HTML标签,否则程序将抛出NullPointerException异常。
1.1.3 模块 前面提到那段nocache.js代码用来加载GWT工程的模块。 Smallgwt.gwt.xml中的内容: GWT模块名都是以.gwt.xml结尾的XML文件,用来配置这个工程,位于工程根目录下。 1.1.4 标准GWT工程结构 使用标准的GWT工程结构可以有效的区分客户端和服务器端的代码,所以推荐使用标准的GWT工程结构。如下表: 包名 作用 \ 工程的根目录,包含模块XML文件 client\ 存放客户端代码 server\ 存放服务器端代码 public\ 存放静态资源,如.html,.css,图片等 第二节 Java语言和库的兼容性 1.2.1 语言支持 GWT支持大部分的Java核心语言的语法和语义,但是还有一些小小的不同。GWT程序的目标语言是JavsScript,所以在开发模式和发布模式运行之间还是有一些需要注意的地方。 l 内置类型:简单类型(boolean, byte, char, short, int, long, float, 和double),Object, String, arrays,用户定义类等,都是被支持的。只是有点注意。 n 数学类型:JavaScript中只有64位浮点型。所有的java基本算术类型(除了长整数)在发布模式下都要转换到这个64位浮点数。这就意味着byte、char、short和int等类型的不会象在java中一样溢出。也就是说,这些数据类型的值可以超出合法的取值范围。浮点数运算将作为double运算进行,并且结果的精度将更高。整数除法的执行结果将被四舍五入到整数值。 n long类型:Javascript没有64位整数类型。GWT使用两个32位整数类型完成64为整数的模拟。这就有两个问题:首先是性能问题,另外就是JSNI code代码是不支持的。 l 异常:try, catch, finally和用户定义的异常还是正常支持的,不过Throwable.getStackTrace()是不被发布模式支持的。 注意:有几个Java VM中最基本的异常是不会在发布模式中支持的,那就是NullPointerException、 StackOverflowError和OutOfMemoryError。取而代之的是JavaScriptException异常。 l 断言:在开发模式中断言语句总是不起作用的,因为它和在调试的时候GWT库提供的众多很有帮助的错误检测差的很远。GWT编译器默认移除和忽略所有的断言。但是可以通过为编译器指定-ea标志在发布模式下打开这些断言。 l 多线程和同步:Javascript解释器是单线程的,所以GWT只是单纯的接收同步的关键字,而不起任何作用。同步相关的方法也不被接收,包括Object.wait()、Object.notify()和Object.notifyAll()。编译器将忽略同步关键字除非对象关联一个同步方法被调用。 l 反射(Reflection):为了最大化效率,GWT编译Java源代码到一个整块的script,不支持类的连续动态调入。对这个的优化通常使用反射机制。不管如何,通过类的名字Object.getClass().getName()得到一个类对象是可以的。 l Java析构函数(Finalization):JavaScript不支持对象被回收时的析构函数调用,所以GWT在发布模式中不会支持Java析构函数。 l 精确浮点数:Java语言规范定义浮点数支持,包括单精度和上精度都支持精确浮点(strictf)关键字。GWT不支持这个关键字,不能保证任何精度的浮点运算,所以不要再客户端进行类似的精度要求较高的浮点运算。 1.2.2 运行库支持 GWT只支持J2SE和J2EE类库的一个小的子集,一个大的完整的集合是不可能被浏览器支持的。要知道那些核心Java运行包被支持,需要查询JRE Emulation Reference。 网址是http://code.google.com/intl/zh-CN/webtoolkit/doc/latest/RefJreEmulation.html 提示:一定要搞清楚那些类是可以转换到客户端代码的。否则会出现很多问题。建议多看这个网页。另外开发模式会帮助你检查那些是可以用的,那些是不可以用的。所以要经常的,尽量早的运行程序。 1.2.3 JRE和模拟类的不同 GWT模拟类和标准的Java运行时有一些差别: l 正则表达式:Java正则表达式的语法和JavaScript正则表达式的语法基本相似。但是还是有一点儿不同,例如:replaceAll和split方法正则表达式。所以使用的时候要注意你的Java正则表达式在JavaScript中要有相同的意义。 l 串行化:Java的串行化机制有一部分不支持编译到JavaScript,例如动态类的调入和反射。结果是,GWT不支持标准的Java串行化。代之的是GWT提供一个简单的自动对象串行化用于调用服务器端的远程方法。 1.2.4 提供的相似功能的类 有一些类的功能对虚拟机来说太复杂,所以提供另一个包来提供相似的功能。这有一些本地JRE的功能子集。 · com.google.gwt.i18n.client.DateTimeFormat : 支持java.util.DateTimeFormat的子集。 · com.google.gwt.i18n.client.NumberFormat : 支持java.util.NumberFormat的子集。 · com.google.gwt.user.client.rpc :一个标记类,提供GWT RPC中使用java.io.Serializable。 · com.google.gwt.user.client.Timer :一个简单的,浏览器安全的定时器类。这个类提供与java.util.Timer相似的功能,但是更简单,因为是单线程环境的。 第三节 历史 1.3.1 GWT的历史机制 GWT的历史机制和其它Ajax的历史实现是基本一样的,都是RSH (Really Simple History)。基本前提的在url标示符片段中保持跟踪程序的内部状态。这种更新操作不会触发页面的重新调入。 这个步骤有一系列的好处: l 这种方法是仅有的一个方法可以可靠的控制浏览器的历史。 l 这种方法可以给用户一个很好的反馈。 l 这种方法支持收藏夹(也叫书签)机制。也就是说可以创建当前状态的收藏夹,保存它,email它等。 1.3.2 历史记号 GWT包含一个机制帮助Ajax开发者激活浏览器的历史功能。为了是每一个页面是可以在历史中导航的,程序为每一个页面生成一个唯一的历史标记。一个标记是一个简单的字符串,程序能够分解这个字符串返回一个详细的状态。这个标记将以URL片段(在地址栏中,在#的后面)的形式保存在浏览器的历史中。这个片段将在用户点击回退和前进的时候返回给程序。 例如,一个名字为"page1"的历史标记添加到了URL中,如下所示: http://www.example.com/com.example.gwt.HistoryExample/HistoryExample.html#page1 当程序要将一个历史标记添加到浏览器的历史栈中的时候,只需要简单的调用History.newItem(token)。当用户点击回退的时候,会调用任意设置了History.addValueChangeHandler()事件句柄的对象的相关方法。 1.3.3 历史实例 要使用GWT的历史支持,你必须首先嵌入一个iframe到host HTML页面中。 然后,在GWT程序中完成下面的步骤: 当想要使用历史事件的时候添加历史标记的历史栈。 创建一个实现了ValueChangeHandler接口的对象,调用ValueChangeEvent.getValue()解析一个新的标记并改变程序的状态到匹配的那个位置。 下面是一个简单的例子演示一个TabPanel中如何在用户选择新的页时添加相应的历史事件。 package com.example.smallgwt.client; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.user.client.History; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TabPanel; public class Smallgwt implements EntryPoint { TabPanel tabPanel; /** * This is the entry point method. */ public void onModuleLoad() { tabPanel = new TabPanel(); tabPanel.add(new HTML("

Page 0 Content: Llamas

"), " Page 0 "); tabPanel.add(new HTML("

Page 1 Content: Alpacas

"), " Page 1 "); tabPanel.add(new HTML("

Page 2 Content: Camels

"), " Page 2 "); tabPanel.addSelectionHandler(new SelectionHandler() { public void onSelection(SelectionEvent event) { // TODO Auto-generated method stub History.newItem("page" + event.getSelectedItem()); } }); History.addValueChangeHandler(new ValueChangeHandler() { public void onValueChange(ValueChangeEvent event) { String historyToken = event.getValue(); // Parse the history token try { if (historyToken.substring(0, 4).equals("page")) { String tabIndexToken = historyToken.substring(4, 5); int tabIndex = Integer.parseInt(tabIndexToken); // Select the specified tab panel tabPanel.selectTab(tabIndex); } else { tabPanel.selectTab(0); } } catch (IndexOutOfBoundsException e) { tabPanel.selectTab(0); } } }); tabPanel.selectTab(0); RootPanel.get().add(tabPanel); } } 1.3.4 超链接组件(Hyperlink Widgets) 一个程序当中超链接可以很容易的整合历史支持。GWT的超链接(Hyperlink)组件看起来就是普通的HTML锚。可以将这个锚关联一个历史标记,当点击这个锚的时候历史标记就自动的添加到浏览器的历史栈中。History.newItem(token)调用将是自动的。 观察上面的例子,程序添加形如“page(n)”的历史标记。在这个程序中基本可以完成任务。不过更复杂的情况就无法完成了。所以推荐象下面一样使用历史标记: page=;session= 可以告诉程序到第几页去同时可以用作为一个关键字使得可以到数据库中查找以前用户的数据。可以作更复杂的历史操作,可读性也更好。 1.3.5 处理onValueChange()回调 在ValueChangeHandler中处理onValueChange()回调的第一步是使用ValueChangeEvent.getValue()得到历史标记。然后需要精确(需要仔细的设计算法)的找到相关的标记。 当onValueChange()方法被调用的时候,程序必须处理两种可能: 1. 程序刚好启动并且已经加入了历史标记 2. 程序正在运行并且已经加入了历史标记 第一点说明,程序在处理状态标记之前必须正确的初始化自己。第二点说明,程序的某些部分或许需要重新初始化。 第四节 数字和日期格式 GWT不提供Java格式类(java.text.DateFormat、java.text.DecimalFormat、 java.text.NumberFormat和java.TimeFormat)的完整虚拟。代之的是一个功能子集com.google.gwt.i18n.client.NumberFormat和com.google.gwt.i18n.client.DateTimeFormat. 要使用NumberFormat或DateTimeFormat类,需要更新模块配置,引入继承如下: 1.4.1 使用NumberFormat 使用NumberFormat类时不需要直接实例化该类,只需要调用它的静态方法get...Format()得到一个实例。通常使用缺省的数字格式: NumberFormat fmt = NumberFormat.getDecimalFormat(); double value = 12345.6789; String formatted = fmt.format(value); // Prints 1,2345.6789 in the default locale GWT.log("Formatted string is" + formatted, null); 这个类还支持将一个数字字符串转换成double浮点数: double value = NumberFormat.getDecimalFormat().parse("12345.6789"); GWT.log("Parsed value is" + value, null); 这个类还支持科学计数法: double value = 12345.6789; String formatted = NumberFormat.getScientificFormat().format(value); // prints 1.2345E4 in the default locale GWT.log("Formatted string is" + formatted, null); 还可以指定自己的数字格式: double value = 12345.6789; String formatted = NumberFormat.getFormat("000000.000000").format(value); // prints 012345.678900 in the default locale GWT.log("Formatted string is" + formatted, null); 数字的格式符号如下: Symbol Meaning 0 Digit, zero forced # Digit, zero shows as absent . Decimal separator or monetary decimal separator - Minus sign , Grouping separator 指定一个非法的模式会导致方法NumberFormat.getFormat()抛出一个异常,那就是java.lang.IllegalArgumentException。如果多次使用相同的模式,则可以使用在很多NumberFormat.getFormat(pattern)的返回值缓存格式。 1.4.2 日期时间格式 GWT提供DateTimeFormat类来替代标准JRE的DateFormat和TimeFormat类的功能。 类DateTimeFormat有一大批的预定义格式。 Date today = new Date(); // prints Tue Dec 18 12:01:26 GMT-500 2007 in the default locale.     GWT.log(today.toString(), null); // prints 12/18/07 in the default locale GWT.log(DateTimeFormat.getShortDateFormat().format(today), null); // prints December 18, 2007 in the default locale GWT.log(DateTimeFormat.getLongDateFormat().format(today), null); // prints 12:01 PM in the default locale GWT.log(DateTimeFormat.getShortTimeFormat().format(today), null); // prints 12:01:26 PM GMT-05:00 in the default locale GWT.log(DateTimeFormat.getLongTimeFormat().format(today), null); // prints Dec 18, 2007 12:01:26 PM in the default locale GWT.log(DateTimeFormat.getMediumDateTimeFormat().format(today), null); 就像NumberFormat类,这个类也可以将一个日期字符串转换成一个日期。也可以自己定义格式化显示时间日期的格式。 第五节 编程定时逻辑 1.5.1 计划工作:Timer类 创建一个定时器(timer)类实例,然后重写run()方法。 Timer timer = new Timer() {   public void run() {   Window.alert ("Timer expired!"); } }; // Execute the timer to expire 2 seconds in the future timer.schedule(2000); Notice that the timer will not have a chance to execute the run() method until after control returns to the JavaScript event loop. 1.5.2 创建超时逻辑 设置一个长时间运行的命令的超时时间是一个典型的应用。记住下面四点: 1. 保存一个定时器到一个实例变量中 2. 开始计时之前一定检测定时器是否运行(检测实例变量是否为空)。 3. 当命令成功完成后要记得取消定时器。 4. 当命令完成或者定时器实效时总是设置实例变量为空 下面是一个超时使用的例子,在Remote Procedure Call (RPC)的时候。 import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; public class Foo { // A keeper of the timer instance in case we need to cancel it private Timer timeoutTimer = null; // An indicator when the computation should quit private boolean abortFlag = false; static final int TIMEOUT = 30; // 30 second timeout void startWork () { // ... // Check to make sure the timer isn't already running. if (timeoutTimer != null) { Window.alert("Command is already running!"); return; } // Create a timer to abort if the RPC takes too long timeoutTimer = new Timer() { public void run() { Window.alert("Timeout expired."); timeoutTimer = null; abortFlag = true; } }; // (re)Initialize the abort flag and start the timer. abortFlag = false; timeoutTimer.schedule(TIMEOUT * 1000); // timeout is in milliseconds // Kick off an RPC myService.myRpcMethod(arg, new AsyncCallback() { public void onFailure(Throwable caught) { Window.alert("RPC Failed:" + caught); cancelTimer(); } public void onSuccess(Object result) { cancelTimer(); if (abortFlag) { // Timeout already occurred. discard result return; } Window.alert ("RPC returned: "+ (String)result); } } } // Stop the timeout timer if it is running private void cancelTimer() { if (timeoutTimer != null) { timeoutTimer.cancel(); timeoutTimer = null; } } } 1.5.3 周期性运行逻辑 为了保持用户界面的更新,需要在一些时候周期性的执行更新。或许向服务器运行一个轮训检测新数据,或者需要更新屏幕动画。这个时候,就可以使用Timer类的scheduleRepeating()方法: public class Foo { // A timer to update the elapsed time count private Timer elapsedTimer; private Label elapsedLabel = new Label(); private long startTime; public Foo () { // ... Add elapsedLabel to a Panel ... // Create a new timer elapsedTimer = new Timer () { public void run() { showElapsed(); } }; startTime = System.currentTimeMillis(); // Schedule the timer for every 1/2 second (500 milliseconds) elapsedTimer.scheduleRepeating(500); // ... The elapsed timer has started ... } /** * Show the current elapsed time in the elapsedLabel widget. */ private void showElapsed () { double elapsedTime = (System.currentTimeMillis() - startTime) / 1000.0; NumberFormat n = NumberFormat.getFormat("#,##0.000"); elapsedLabel.setText("Elapsed: " + n.format(elapsedTime)); } } 1.5.4 延迟当前的一些逻辑:使用DeferredCommand类 使用这个功能需要: 创建Command类的子类,然后重写它的execute()方法。 将这个类添加到 有时候可能需要中断当前的逻辑循环以便于JavaScript时间循环得到执行另一端代码的机会。DeferredCommand.addCommand()中。例子如下: TextBox dataEntry; // Set the focus on the widget after setup completes. DeferredCommand.addCommand(new Command() { public void execute () { dataEntry.setFocus(); } } dataEntry = new TextBox(); 1.5.5 避免Slow Script警告:IncrementalCommand类使用 AJAX开发者需要一直都让用户知道浏览器对自己的操作作出了相应。当JavaScript代码正在运行的时候,用户组件(例如按钮,文本框等)不会响应用户的任何输入。如果浏览器一直这样,用户可能会觉得浏览器挂了。不过浏览器有一个内建的机制,那就是unresponsive script warning。如下图: 任何代码运行超过10秒钟,没有将控制交回给JavaScript主时间循环,浏览器都回弹出这个对话框给用户。 GWT提供一个IncrementalCommand类帮助解决长时间运行计算任务问题。它会反复的执行execute()知道计算结束。 下面的例子展示了如何使用IncrementalCommand类做一些计算,同时浏览器还对用户作出响应: public class IncrementalCommandTest implements EntryPoint { // Number of times doWork() is called static final int MAX_LOOPS = 10000; // Tight inner loop in doWork() static final int WORK_LOOP_COUNT = 50; // Number of times doWork() is called in IncrementalCommand before // returning control to the event loop static final int WORK_CHUNK = 100; // A button to kick off the computation Button button; public void onModuleLoad() { button = new Button("Start Computation"); button.addClickHandler(new ClickHandler () { public void onClick(ClickEvent event) { doWorkIncremental(); } } } /** * Create a IncrementalCommand instance that gets called back every so often * until all the work it has to do is complete. */ private void doWorkIncremental () { // Turn off the button so it won't start processing again. button.setEnabled(false); IncrementalCommand ic = new IncrementalCommand(){ int counter = 0; public boolean execute() { for (int i=0;i,而不是 Java 的 Class 。注意 Key 只能用 String 表示。 例如,一个 Address 对象包含如下 Key-Value: city:Beijing street:Chaoyang Road postcode:100025(整数) 用 JSON 表示如下: {"city":"Beijing","street":" Chaoyang Road ","postcode":100025} 其中 Value 也可以是另一个 Object 或者数组,因此,复杂的 Object 可以嵌套表示,例如,一个 Person 对象包含 name 和 address 对象,可以表示如下: {"name":"Michael","address": {"city":"Beijing","street":" Chaoyang Road ","postcode":100025} } 1.6.2 JavaScript 处理 JSON 数据 上面介绍了如何用 JSON 表示数据,接下来,我们还要解决如何在服务器端生成 JSON 格式的数据以便发送到客户端,以及客户端如何使用 JavaScript 处理 JSON 格式的数据。 我们先讨论如何在 Web 页面中用 JavaScript 处理 JSON 数据。我们通过一个简单的 JavaScript 方法就能看到客户端如何将 JSON 数据表示给用户: function handleJson() { var j={"name":"Michael","address": {"city":"Beijing","street":" Chaoyang Road ","postcode":100025} }; document.write(j.name); document.write(j.address.city); } 假定服务器返回的 JSON 数据是上文的: {"name":"Michael","address": {"city":"Beijing","street":" Chaoyang Road ","postcode":100025} } 只需将其赋值给一个 JavaScript 变量,就可以立刻使用该变量并更新页面中的信息了,相比 XML 需要从 DOM 中读取各种节点而言,JSON 的使用非常容易。我们需要做的仅仅是发送一个 Ajax 请求,然后将服务器返回的 JSON 数据赋值给一个变量即可。有许多 Ajax 框架早已包含了处理 JSON 数据的能力,例如 Prototype(一个流行的 JavaScript 库:http://prototypejs.org)提供了 evalJSON() 方法,能直接将服务器返回的 JSON 文本变成一个 JavaScript 变量: new Ajax.Request("http://url", { method: "get", onSuccess: function(transport) { var json = transport.responseText.evalJSON(); // TODO: document.write(json.xxx); } }); 1.6.3 服务器端输出 JSON 格式数据 下面我们讨论如何在服务器端输出 JSON 格式的数据。以 Java 为例,我们将演示将一个 Java 对象编码为 JSON 格式的文本。 将 String 对象编码为 JSON 格式时,只需处理好特殊字符即可。另外,必须用 (") 而非 (') 表示字符串: static String string2Json(String s) { StringBuilder sb = new StringBuilder(s.length()+20); sb.append('\"'); for (int i=0; i 编码为 JSON 格式,因为 JavaScript 的 Object 实际上对应的是 Java 的 Map 。该方法如下: static String map2Json(Map map) { if (map.isEmpty()) return "{}"; StringBuilder sb = new StringBuilder(map.size() << 4); sb.append('{'); Set keys = map.keySet(); for (String key : keys) { Object value = map.get(key); sb.append('\"'); sb.append(key); sb.append('\"'); sb.append(':'); sb.append(toJson(value)); sb.append(','); } // 将最后的 ',' 变为 '}': sb.setCharAt(sb.length()-1, '}'); return sb.toString(); } 为了统一处理任意的 Java 对象,我们编写一个入口方法 toJson(Object),能够将任意的 Java 对象编码为 JSON 格式: public static String toJson(Object o) { if (o==null) return "null"; if (o instanceof String) return string2Json((String)o); if (o instanceof Boolean) return boolean2Json((Boolean)o); if (o instanceof Number) return number2Json((Number)o); if (o instanceof Map) return map2Json((Map)o); if (o instanceof Object[]) return array2Json((Object[])o); throw new RuntimeException("Unsupported type: " + o.getClass().getName()); } 我们并未对 Java 对象作严格的检查。不被支持的对象(例如 List)将直接抛出 RuntimeException 。此外,为了保证输出的 JSON 是有效的,Map 对象的 Key 也不能包含特殊字符。细心的读者可能还会发现循环引用的对象会引发无限递归,例如,精心构造一个循环引用的 Map,就可以检测到 StackOverflowException: @Test(expected=StackOverflowError.class) public void testRecurrsiveMap2Json() { Map map = new HashMap(); map.put("key", map); JsonUtil.map2Json(map); } 好在服务器处理的 JSON 数据最终都应该转化为简单的 JavaScript 对象,因此,递归引用的可能性很小。 最后,通过 Servlet 或 MVC 框架输出 JSON 时,需要设置正确的 MIME 类型(application/json)和字符编码。假定服务器使用 UTF-8 编码,则可以使用以下代码输出编码后的 JSON 文本: response.setContentType("application/json;charset=UTF-8"); response.setCharacterEncoding("UTF-8"); PrintWriter pw = response.getWriter(); pw.write(JsonUtil.toJson(obj)); pw.flush(); JSON 已经是 JavaScript 标准的一部分。目前,主流的浏览器对 JSON 支持都非常完善。应用 JSON,我们可以从 XML 的解析中摆脱出来,对那些应用 Ajax 的 Web 2.0 网站来说,JSON 确实是目前最灵活的轻量级方案。 第二章 构建用户界面 第一节 基本UI组件 组件通常用来实现程序的输入和输出。GWT提供了很多的组件。 2.1.1 Button public void onModuleLoad() { Button btn = new Button("button");// new Button("button"); RootPanel.get().add(btn); } 改成后面的内容看一下效果。Button构造函数中可以放的并不限于一个按钮的标签。还可以放任意的HTML。可以到帮助文档中查看构造函数就明白了。那么如何将时间处理和这个按钮联系起来呢?看下面的程序: public void onModuleLoad() { Button btn = new Button("button",new ClickHandler(){ @Override public void onClick(ClickEvent event) { // TODO Auto-generated method stub Window.alert("hello world!"); } }); RootPanel.get().add(btn); } 在Button的构造函数中引入第二个参数。就是一个ClickHandler类的实例,然后重写其中的onClick方法。 当然也可以单独的定义这个类,然后在创建这个按钮之后在设置这个按钮的事件处理类。例如: Button btn = new Button("button"); class MyHandler implements ClickHandler{ @Override public void onClick(ClickEvent event) { // TODO Auto-generated method stub Window.alert("hello world!"); } } btn.addClickHandler(new MyHandler()); RootPanel.get().add(btn); 2.1.2 Textbox public void onModuleLoad() { TextBox tbx = new TextBox(); tbx.setText("hello world"); class keyevent implements KeyPressHandler{ @Override public void onKeyPress(KeyPressEvent event) { // TODO Auto-generated method stub Window.alert("hello world!"); } } tbx.addKeyPressHandler(new keyevent()); RootPanel.get().add(tbx); } 2.1.3 TextArea 在上一个例子上稍微改一下如下: TextArea ta = new TextArea(); ta.setCharacterWidth(80); ta.setVisibleLines(50); class keyevent implements KeyPressHandler{ @Override public void onKeyPress(KeyPressEvent event) { // TODO Auto-generated method stub if(!Character.isDigit(event.getCharCode())){ ((TextArea) event.getSource()).cancelKey(); } } } ta.addKeyPressHandler(new keyevent()); RootPanel.get().add(ta); } 现在可以自己玩儿以下PasswordTextBox了 2.1.4 Tree TreeItem root = new TreeItem("root"); root.addItem("item0"); root.addItem("item1"); root.addItem("item2"); TreeItem item = new TreeItem(new CheckBox("item3")); root.addItem(item); Tree t = new Tree(); t.addItem(root); class sh implements SelectionHandler{ @Override public void onSelection(SelectionEvent event) { // TODO Auto-generated method stub Window.alert(event.getSelectedItem().toString()); } } t.addSelectionHandler(new sh()); RootPanel.get().add(t); 2.1.5 注意事项 可以无限的使用GWT提供的组件集合。甚至自定义组件。 l 可以使用Composite组件将已经存在的组件组合在一起。 l 可以写GWT绑定到一个存在的JavaScript组件 l 可以使用Java或者Javascript创建符合自己使用要求的组件 同时还可以使用很多第三方GWT组件。 第二节 组件布局 有了上一节的内容,很显然接下来应该学习如何将各种组件按照一定的规则显示出来。 2.2.1 基本布局面板 GWT提供了一个丰富的布局面板集合。它们是: RootPanel:一个RootPanel是一个最顶层面板。左右的布局,组件都要放到它里面。RootPanel.get()得到一个HTML文档中元素的唯一面板。使用RootPanel.get(String id)得到页面中任何的一个命名的元素。 FlowPanel:一个FlowPanel是一个最简单的面板。他创建一个简单的
元素,然后将子元素不加任何修改的直接放进去。当想要使用HTML布局子组件的时候使用这个面板。 HTMLPanel:这个面板提供了一个简单的定义HTML结构的途径,其中的组件将内嵌到定义好的地方。使UiBinder模版的时候可能需要直接使用这个布局面板。 FormPanel:当需要处理和HTML表单类似的行为的时候, 可以使用FormPanel。这个布局面板将内部的任意组件包装在
元素当中。 ScrollPanel:当要在其它的布局面板中创建一个滚动区域的时候使用这个布局面板。使用这个面板需要明确指出多大尺寸时需要滚动。 PopupPanel和DialogBox:使用这两个面板创建简单的弹出模式对话框。它们覆盖浏览器窗口已经存在的内容之上。 Grid和FlexTable:这两个组件用来创建出传统的HTML 元素,可以将任意的组件添加到它们的单元格当中。 2.2.2 高级布局面板 GWT 2.0 引入了许多新的布局面板。 RootLayoutPanel:这个布局面板是唯一的一个根容器,其它的布局容器都要放到其中。可以将任意数量的子布局,自组件随意的放到里面。通常情况下是将这个容器作为一个组织其它容器的容器,下面的代码片段就是的DockLayoutPanel填满整个的浏览器客户区域。 DockLayoutPanel appPanel = new DockLayoutPanel(Unit.EM); RootLayoutPanel.get().add(appPanel); LayoutPanel:最基本的高级布局面板。是最接近AbsolutePanel的布局面板。也是使用最灵活的布局面板,可以任意条件对内部的组件进行布局。看如下代码: Widget child0 = new HTML("child0"), child1 = new HTML("child1"), child2 = new HTML("child2"); LayoutPanel p = new LayoutPanel(); p.add(child0); p.add(child1); p.add(child2); p.setWidgetLeftWidth(child0, 0, Unit.PCT, 50, Unit.PCT); // Left panel p.setWidgetRightWidth(child1, 0, Unit.PCT, 50, Unit.PCT); // Right panel p.setWidgetLeftRight(child2, 5, Unit.EM, 5, Unit.EM); // Center panel p.setWidgetTopBottom(child2, 5, Unit.EM, 5, Unit.EM); RootLayoutPanel.get().add(p); child0、child1、child2为任意GWT组件。 DockLayoutPanel:这个布局面板提供预DockPanel组件相同的效果,使用这个布局可以组织网页的结构而不是用 tables。看下面的程序: DockLayoutPanel p = new DockLayoutPanel(Unit.EM); p.addNorth(new HTML("header"), 2); p.addSouth(new HTML("footer"), 2); p.addWest(new HTML("navigation"), 10); p.add(new HTML(content)); SplitLayoutPanel:这个布局面板类似于DockLayoutPanel,事实上也是DockLayoutPanle扩展而来的。不同的是这个布局会在一对子组件之间创建一个用户可以拖拽的分割条。通常使用的是这个类的两个分支,就是HorizontalSplitPanel和VerticalSplitPanel。 SplitLayoutPanel p = new SplitLayoutPanel(); p.addWest(new HTML("navigation"), 128); p.addNorth(new HTML("list"), 384); p.add(new HTML("details")); StackLayoutPanel:这个布局取代StackPanel。它每一次只显示一个子组件,每一个子组件都关联一个"header"组件。点击组件的头组件,相关的子组件就被显示。 StackLayoutPanel p = new StackLayoutPanel(Unit.EM); p.add(new HTML("this content"), new HTML("this"), 4); p.add(new HTML("that content"), new HTML("that"), 4); p.add(new HTML("the other content"), new HTML("the other"), 4); TabLayoutPanel:替换TabPanel组件的,TabLayoutPanel在一行中显示子组件的标签。点击响应的标签,对应的子组件就显示出来。 TabLayoutPanel p = new TabLayoutPanel(1.5, Unit.EM); p.add(new HTML("this content"), "this"); p.add(new HTML("that content"), "that"); p.add(new HTML("the other content"), "the other"); 在GWT2.0中要使用layoutpanel。而不要使用以前的panel组件。 与服务器通信 在客户端定义一个接口,继承自RemoteService并且列出所有的RPC方法 在服务器端定义一个类继承RemoteServiceservlet并实现上面定义的接口。 在客户端定义一个asynchronous接口便于在客户端调用服务器端的服务。 定义一个同步接口 创建一个客户端Java接口,继承RemoteService接口。 l 必须继承com.google.gwt.user.client.rpc.RemoteService接口 l 所有参数和返回值必须是可串行化的 l 服务接口必须置于client目录下 package com.example.foo.client; import com.google.gwt.user.client.rpc.RemoteService; public interface MyService extends RemoteService {   public String myMethod(String s); } 服务器端这个服务的任何实现都必须继承RemoteServiceServlet并实现上面的那个接口。 l 必须继承com.google.gwt.user.server.rpc.RemoteServiceServle类 l 必须实现响应的服务接口 l 由于实现类放在服务器端为客户端提供服务,所以它不需要转换成JavaScript代码,因此不能放在client目录中,应放在server目录下 package com.example.foo.server; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.example.client.MyService; public class MyServiceImpl extends RemoteServiceServlet implements MyService {   public String myMethod(String s) {   // Do something interesting with 's' here on the server.   return s; } } 这时在客户端直接调用这个RPC是不可能的。必须创建一个异步接口,象下面这样: 创建异步接口 在使用这个RPC之前,必须创建另一个客户端接口。一个基于原始服务接口的异步接口。 l 异步接口必须与服务接口在同一个路径下,即异步接口要在client目录下。 l 异步接口名等于服务接口名加上“Async”后缀。 l 异步方法与同步方法的方法名相同。并且比服务方法多了一个AsyncCallback参数,返回类型是void。 package com.example.foo.client; interface MyServiceAsync {   public void myMethod(String s, AsyncCallback callback); } GWT开发模式包含一个内置的jetty作为开发时的servlet容器。这使得可以在开发模式下使用Java debugger调试服务器端和客户端代码。要自动调入实现的服务,需要在web.xml中配置servlet。例如: 创建一个模型为com.example.gwtfirst。 定义RPC接口为com.example.gwtfirst.client.GreetingService。 然后annontion为@RemoteServiceRelativePath("greet")。 对应的服务器端实现为:com.example.gwtfirst.server.GreetingServiceImpl 则对应的web.xml为: greetServlet com.example.gwtfirst.server.GreetingServiceImpl greetServlet /gwtfirst/greet 也可以在web.xml中使用全称如: /com.example.gwtfirst.Gwtfirst/greet 同时需要修改模块配置文件Gwtfirst.gwt.xml。将模块重命名去掉,采用默认模块名。 并修改HTML文件的引入javascript入口文件如下: 实例:创建一个RPC 使用GWT.create()实例化一个服务接口 创建一个异步callback对象来接收RPC完成通知 调用 // The RemoteServiceRelativePath annotation automatically calls setServiceEntryPoint() @RemoteServiceRelativePath("email") public interface MyEmailService extends RemoteService {   void emptyMyInbox(String username, String password); } 实现对应的异步接口 package com.example.gwtfirst.client; import com.google.gwt.user.client.rpc.AsyncCallback; public interface MyEmailServiceAsync { void emptyMyInbox(String username, String password, AsyncCallback callback); } 实现服务器端对应的方法 package com.example.gwtfirst.server; import com.example.gwtfirst.client.MyEmailService; import com.google.gwt.user.server.rpc.RemoteServiceServlet; public class MyEmailServiceImpl extends RemoteServiceServlet implements MyEmailService { @Override public String emptyMyInbox(String username, String password) { // TODO Auto-generated method stub if(username.trim().equalsIgnoreCase(password.trim())) return "ok"; else return "faile"; } } 然后在入口点中调用 final TextBox mytxt = new TextBox(); Button mybutton = new Button("mytxt"); RootPanel.get("mytxt").add(mytxt); RootPanel.get("mybutton").add(mybutton); class MyPasswdHandler implements ClickHandler{ /** * Fired when the user clicks on the sendButton. */ public void onClick(ClickEvent event) { MyEmailServiceAsync Mesa = GWT.create(MyEmailService.class); Mesa.emptyMyInbox("ok","ok",new AsyncCallback() { public void onFailure(Throwable caught) { // Show the RPC error message to the user mytxt.setText("wl"); } public void onSuccess(String result) { mytxt.setText("result"); } }); } } mybutton.addClickHandler(new MyPasswdHandler()); 最后在web.xml中加入相关配置 mailServlet com.example.gwtfirst.server.MyEmailServiceImpl mailServlet /gwtfirst/email 注意: 如果开发模式启动程序,控制台得到ClassNotFoundException异常。则意味着GWT模块中servlet引用的类不在“war/WEB-INF/classes”中。 如果开发模式启动程序,控制台得到NoClassDefFoundError: com/google/gwt/user/client/rpc/RemoteService,则意味着没有将gwt-servlet.jar放到“war/WEB-INF/lib”下面。 当调用RPC时显示NoServiceEntryPointSpecifiedException: Service implementation URL not specified。则说明没有在服务接口中指定@RemoteServiceRelativePath。 如果调用RPC,得到失败信息为“404 StatusCodeException”。则说明没有被指web.xml。需要在web.xml中指定@RemoteServiceRelativePath 和对应的。 GWT还提供了一个HTTP客户端类。可以通过JSON或者XML进行通信。 要使用GWT的HTTP需要在模型的xml文件中添加继承如下: RequestBuilder 是创建和发送HTTP请求的核心类。它有代参数的构造函数来创建一个指定的HTTP请求 (GET, POST, 等)和URL 工具类。一旦有了RequestBuilder对象,就可以使用它的方法设置用户名、密码和超时时间。同时还可以设置任意的HTTP请求头部信息。 一旦HTTP请求准备好了,就可以使用sendRequest(String, RequestCallback)方法将请求发送到服务器。RequestCallback参数将会得到请求结束后服务器的响应信息或者出错信息。 请求完成后,onResponseReceived(Request, Response)方法将会被调用。详细的响应信息(状态码、HTTP头和响应内容等)可以通过Response参数获得。需要注意的是并不是只有状态码为200(通常表示成功)。只要请求正常的完成了,就会调用这个方法。另一方面,如果调用没有完成,则onError(Request, Throwable)方法会被调用,第二个参数为失败原因。 下面是一个简单的例子: import com.google.gwt.http.client.*; String url = "http://www.myserver.com/getData?type=3"; RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, URL.encode(url)); try {   Request request = builder.sendRequest(null, new RequestCallback() { public void onError(Request request, Throwable exception) {      // Couldn't connect to server (could be timeout, SOP violation, etc.) } public void onResponseReceived(Request request, Response response) { if (200 == response.getStatusCode()) {     // Process the response in response.getText()     } else {     // Handle the error.  Can get the status text from response.getStatusText() } } }); } catch (RequestException e) {   // Couldn't connect to server         } http://code.google.com/intl/zh-CN/webtoolkit/doc/latest/DevGuideCodingBasicsDelayed.html

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享文档获得金币 ]
27 人已下载

下载文档

相关文档