使用 oracle jdeveloper 构建您的第一个 gwt web 应用程序
到目前为止,您已经了解了 gwt 的工作方式;现在,让我们编码示例 web 应用程序。
示例应用程序是一个工作列表管理器。其特性十分简单:创建、编辑、删除工作列表并对其进行优先级排列。我们选择了该示例是因为它很容易理解,然而其实施涵盖了大量 gwt 的特性。
下面是最终应用程序的屏幕快照:
第 1 步:安装 gwt
从 google 的 web 站点 http://code.google.com/webtoolkit/ 下载 gwt。在写本文时,gwt 推出的是 windows 和 linux 版本。gwt 是特定于平台的,因为其托管模式在 firefox 的修改版本中工作,该版本本身依赖于平台。(我们可以在 apple 计算机上成功地使用 gwt 的 linux 版本,但是托管模式不起作用。)
gwt 下载形式是一个归档文件,您必须使用 linux 上的 tar -xvf 命令或者 windows 上的解压缩工具进行解压缩。这就是您安装该工具包需要做的所有工作。
第 2 步:运行 applicationcreator 脚本
打开命令行,转至 gwt 的安装目录。该目录包含 applicationcreator 脚本,我们将使用该脚本启动我们的应用程序。由于我们希望应用程序存储在 oracle technology network 目录中,因此我们将“-out otn”作为参数添加到脚本中。在 linux 上,键入:
./applicationcreator -out otn otn.todo.client.todoapp
在 windows 上,使用:
applicationcreator -out otn otn.todo.client.todoapp
该脚本生成基本的项目结构 — 请求的应用程序类中的示例“hello word”代码以及两个脚本:todoapp-shell(用于在托管模式下运行应用程序)和 todoapp-compile(用于打包应用程序以便在 web 模式下使用)。
第 3 步:在 jdeveloper 中打开项目
启动 jdeveloper 并创建一个新的 web 项目:
单击 next 按钮。jdeveloper 将询问新项目的位置。使用应用程序的名称作为 project name,选择应用程序根目录(如步骤 2 的定义)作为 directory name:
单击 next 按钮,并验证您的应用程序是 j2ee 1.4 应用程序:
单击
next 按钮,并选择您的项目 web 属性:document root 是当前项目的 www 目录,j2ee web application name 和 j2ee context root 都是项目名称:
这将创建 jdeveloper 项目,但是将出现某些编译错误,因为 gwt 的库未包含在项目类路径中。在项目属性中,选择左侧端树的 libraries 节点,并添加 gwt-user.jar 库:
您的项目现在应该可以编译,看起来与以下内容相似:
编写客户端代码
上面的 applicationcreator 脚本创建了一个基本的“hello world”应用程序,可在 otn.todo.client 程序包中使用。下面是其主要方法:
public void onmoduleload() {
final button button = new button("click me");
final label label = new label();
button.addclicklistener(new clicklistener() {
public void onclick(widget sender) {
if (label.gettext().equals(""))
label.settext("hello world!");
else
label.settext("");
}
});
rootpanel.get("slot1").add(button);
rootpanel.get("slot2").add(label);
}
}
该方法将创建一个按钮“click me”。单击该按钮后,将显示“hello world”。
该方法分为三部分:
- 创建 button 和 label 小部件
- 创建 clicklistener 对象。该代码与您用 swing 编写的内容很接近;如果您具有桌面 java 背景则更容易理解。
- 在 html 页上显示小部件:slot1 和 slot2 都是该页上的 html 元素
用作框架的 html 页位于 src/otn/todo/public 目录中。它将两个 html 元素(slot1 和 slot2)定义为表单元格。
在托管模式下运行和调试
现在您已经创建了应用程序并且已经看到其生成的内容,结下来让我们来执行它。
您可以通过从命令行使用 todoapp-shell 脚本轻松地运行该项目。虽然这是启动应用程序的很好途径,但是您可能更喜欢直接从 jdeveloper 内启动应用程序。为此,单击 run 菜单,选择 choose active run configuration > manage run configurations。编辑默认的运行配置并使用以下命令:
- 对于 default run target:使用 com.google.gwt.dev.gwtshell,它在特定于平台的 gwt jar 内。在 linux 上,它类似以下内容:
path.to.your.gwt.installation.directory/gwt-devlinux.jar!/com/google/gwt/dev/gwtshell.class
在 windows 上,它类似以下内容: path.to.your.gwt.installation.directory/gwt-dev-windows.jar!/com/google/gwt/dev/gwtshell.class
- 对于 program arguments,使用:
-out path.to.your.gwt.installation.directory/otn/www otn.todo.todoapp/todoapp.html
- 对于 run directory,使用
path.to.your.gwt.installation.directory/otn
最终结果类似以下内容:
要运行您的应用程序,您必须向其类路径中再添加两个库:gwt 特定于平台的 jar 和应用程序的 src 目录:
您现在应能够从 jdeveloper 运行应用程序了。
这是一个很复杂的设置,但是令人欣慰的是,您可以重新使用它对应用程序进行调试。使用 debug 按钮而不是 run 按钮。然后,您可以象平常一样使用调试器 — 设置断点、逐步执行代码等:
关于该特性给人印象很深的是,您可以通过标准的 jdeveloper 调试器调试用 java 编写的客户端代码。
扩展您的 gwt web 应用程序
现在您已经创建了一个简单的 gwt web 应用程序,让我们通过两个最常用的 gwt 特性对其进行扩展:rpc 机制(该机制允许应用程序调用服务器端代码)和 history 对象(通过该对象,用户可精确处理浏览器的 back 按钮)。
使用 rpc 进行客户端和服务器之间的数据交换
到目前为止,您只创建了应用程序的客户端代码:使用 gwt 编译器,您已经生成了大量 html 和 javascript 文件,它们将在最终用户的浏览器中运行。但是,如果该应用程序不能与服务器通信就没有什么用处了。
使用 gwt,客户端/服务器通信就是对 servlet 进行编码并使其与应用程序通信。下面是您要做的工作。
创建一个定义您的服务的接口。该接口必须扩展 google 的 com.google.gwt.user.client.rpc.remoteservice 接口,并可以放到客户端程序包(本例为 otn.todo.client)中。
然后,对接口进行编码以便允许您在服务器上读取和写入工作列表:
package otn.todo.client;
import java.util.list;
import com.google.gwt.user.client.rpc.remoteservice;
public interface todolistbackupservice extends remoteservice {
/**
* save the to-do list on the server.
*/
void savetodolist(list todolist);
/**
* get the to-do list on the server.
*/
list gettodolist();
}
对 servlet 进行编码。在服务器端,您必须编码出具有以下特征的类:
- 扩展 google 的 com.google.gwt.user.server.rpc.remoteserviceservlet 类(该类反过来会扩展 java 的 javax.servlet.http.httpservlet,有效使其成为 servlet)
- 实施步骤 1 中编写的接口
- 位于服务器程序包(本例为 otn.todo.server)中
package otn.todo.server;
import java.util.arraylist;
import java.util.list;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpsession;
import otn.todo.client.todo;
import otn.todo.client.todolistbackupservice;
import com.google.gwt.user.server.rpc.remoteserviceservlet;
public class todolistbackupserviceimpl extends remoteserviceservlet implements
todolistbackupservice {
private static final string todolist_key = "todolist_key";
public void savetodolist(list todolist) {
httpservletrequest request = this.getthreadlocalrequest();
httpsession session = request.getsession();
session.setattribute(todolist_key, todolist);
}
public list gettodolist() {
httpservletrequest request = this.getthreadlocalrequest();
httpsession session = request.getsession();
if (session.getattribute(todolist_key) == null) {
list todolist = new arraylist();
todo todo = new todo("hello from the server");
todolist.add(todo);
return todolist;
} else {
return (list) session.getattribute(todolist_key);
}
}
}
该 servlet 在用户的 httpsession 中只存储工作列表;这当然是保存数据的基本方法。在一般的应用程序中,您可以使用 jndi 访问 ejb,或者使用任何经典模式从 servlet 访问业务服务。
最后,您必须在 servlet 容器内配置该 servlet。如果您使用的是 gwt shell,您可以在 *.gwt.xml 配置文件中进行配置,本例中该配置文件为 todoapp.gwt.xml:
如果您希望在其他应用服务器(如 oc4j)中对其进行配置,只需将平常的 xml 配置添加到 web-inf/web.xml 文件中即可:
todolistbackupservice
otn.todo.server.todolistbackupserviceimpl
todolistbackupservice
/todolistbackupservice
添加一些粘合剂。我们需要的粘合剂是 async 类,它必须遵循几个规则:
- 位于客户端程序包(otn.todo.client)中。
- 其名称与步骤 1 中描述的接口的名称相同,最后面添加 async。
- 其方法与步骤 1 中描述的接口的方法相同,但是它们都回调一个附加参数 com.google.gwt.user.client.rpc.asynccallback。
package otn.todo.client;
import java.util.list;
import com.google.gwt.user.client.rpc.asynccallback;
public interface todolistbackupserviceasync {
/**
* save the to-do list on the server.
*/
void savetodolist(list todolist, asynccallback callback);
/**
* get the to-do list on the server.
*/
void gettodolist(asynccallback callback);
}
在应用程序内使用该类。要从客户端应用程序内访问服务器端代码,使用 com.google.gwt.core.client.gwt 类,该类可以创建一个很特殊的对象:
todolistbackupserviceasync todolistbackupservice = (todolistbackupserviceasync) gwt.create(todolistbackupservice.class);
这将在运行时创建一个实施两个接口的类:
- 我们刚刚在步骤 3 中进行编码的 async 接口
- google 的 com.google.gwt.user.client.rpc.servicedeftarget 接口
第二个接口用于配置类以便它可以指向步骤 2 中定义的 servlet:
servicedeftarget endpoint = (servicedeftarget) todolistbackupservice; endpoint.setserviceentrypoint("/todolistbackupservice");
现在您已经将该对象配置为可访问服务器端服务,让我们来访问服务。如您在步骤 3 中所见,async 接口允许您通过添加 asynccallback 回调参数访问在服务中定义的所有方法。该参数用于定义应用程序的行为,具体取决于服务器端调用的成功或失败:
asynccallback callback = new asynccallback() {
public void onsuccess(object result) {
printtodolist();
}
public void onfailure(throwable caught) {
window.alert("warning : the to-do list could not be saved on the server. maybe the server is down.");
}
};
让我们把它们全都放在一起。下面是访问 todolistbackupservice 业务服务的两个客户端方法的完整代码:一个用于在服务器端保存工作列表,另一个用于读取该列表:
/**
* update the to-do list with data from the server.
*/
private void updatetodolistfromserver() {
todolistbackupserviceasync todolistbackupservice =
(todolistbackupserviceasync)gwt.create(todolistbackupservice.class);
servicedeftarget endpoint = (servicedeftarget)todolistbackupservice;
endpoint.setserviceentrypoint("/todolistbackupservice");
asynccallback callback = new asynccallback() {
public void onsuccess(object result) {
todolist = (list)result;
savetodolistinhistory();
}
public void onfailure(throwable caught) {
todo todo =
new todo("error!! server could not be reached.");
todolist.add(todo);
savetodolistinhistory();
}
};
todolistbackupservice.gettodolist(callback);
}
/**
* save the to-do list on the server.
*/
private void savetodolistonserver() {
savetodolistinhistory();
todolistbackupserviceasync todolistbackupservice =
(todolistbackupserviceasync)gwt.create(todolistbackupservice.class);
servicedeftarget endpoint = (servicedeftarget)todolistbackupservice;
endpoint.setserviceentrypoint("/todolistbackupservice");
asynccallback callback = new asynccallback() {
public void onsuccess(object result) {
printtodolist();
}
public void onfailure(throwable caught) {
window.alert("warning : the to-do list could not be saved on the server. maybe the server is down.");
}
};
todolistbackupservice.savetodolist(todolist, callback);
}
示例应用程序在启动时进行服务器端调用。该调用将返回用户的 httpsession 中保存的最新工作列表,或者包含“hello from the server”工作的新工作列表:
管理 back 按钮
在高端 web 应用程序中,浏览器的 back 按钮经常断开。经典的 ajax 应用程序不支持返回前一 web 页的标准 web 行为。
另一方面,gwt 允许对 back 按钮进行编程处理。这是一个功能强大却又很难处理的特性,我们将在示例应用程序中对其进行探究。提议是将 back 按钮用作 undo 按钮:单击该按钮将显示最新事件之前的工作列表。同样地,forward 按钮将用作 redo 按钮。
实施 historylistener 接口。要以编程方式管理 back 按钮,gwt 应用程序必须实施 com.google.gwt.user.client.historylistener 接口。这将强制编写 onhistorychanged(string _historytoken) 方法:
public class todoapp implements entrypoint, historylistener {
/**
* this method is called whenever the application's history changes.
*/
public void onhistorychanged(string _historytoken) {
if (integer.parseint(_historytoken) 1 != historytoken) {
if (historymap.get(_historytoken) != null) {
historytoken = integer.parseint(_historytoken);
todolist = (list) historymap.get(_historytoken);
}
}
printtodolist();
}
该方法意味着当浏览器的历史记录更改时接收事件。您必须将其作为监听器添加到 gwt 的 history 对象中。该操作通常在 onmoduleload() 方法中完成,以便 history 对象在启动时正确初始化:
/**
* this is the entry point method.
*/
public void onmoduleload() {
history.addhistorylistener(this);
}
现在,每次浏览器的历史记录更改时都会调用 onhistorychanged(string _historytoken) 方法。
该方法可以根据作为参数传递的令牌重新创建应用程序的状态。本例中,您将使用该令牌作为密钥来查找存储在历史地图中的工作列表。
向历史记录中添加条目。要使 onhistorychanged(string _historytoken) 方法起作用,您必须预先在历史记录中存储一些条目。
利用 history 对象很容易实现,可以使用其静态 newitem(string historytoken) 方法:
private void savetodolistinhistory() {
list todolistclone = new arraylist();
iterator it = todolist.iterator();
while (it.hasnext()) {
todo todo = (todo) it.next();
todolistclone.add(todo.clone());
}
historymap.put(string.valueof(historytoken), todolistclone);
history.newitem(string.valueof(historytoken));
historytoken ;
}
在本例中,您将应用程序状态存储在了地图中,因此使用历史记录令牌可以找到它。注意,您使用了一个数字作为历史记录令牌,也可以改用任何字符串。
部署您的 web 应用程序
要部署通过 gwt 构建的 web 应用程序,您需要编译客户端代码,在 web 应用程序的 .war 文件中打包结果,然后将 .war 文件部署到相应的应用服务器 oc4j 上。
编译客户端代码
编译客户端代码的方法有多种。使用 applicationcreator 脚本后,gwt 将创建一个名为 todoapp-compile 的 shell 脚本。您可以从命令行启动该脚本。与 todoapp-shell 一样,这是编译应用程序的很好方法;但是,您可能更喜欢直接从 jdeveloper 内部启动该脚本。
另一种编译代码的方法使以托管模式执行应用程序,以便直接从 jdeveloper 编译代码。您的应用程序的窗口的工具栏包含编译/浏览按钮,与下图类似:
在编译过程结束时,您的默认 web 浏览器将打开以便您可以测试结果。gwt 开发 shell 的窗口将显示编译是否成功:
无论您使用何种方法编译代码,您都可以在项目的 www/otn.todo.todoapp 中找到生成的文件。
最后一种编译代码的方法是使用 ant。gwt 不提供特定的 ant 任务,但是您可以通过标准的 java ant 任务启动任何 java 类(例如 gwtcompiler)。首先,定义包含 gwt jar 的路径:
现在,定义专用于编译客户端代码的任务:
classname="com.google.gwt.dev.gwtcompiler"
fork="true">
在属性文件中设置 gwt.output.dir 和 entry.point.class 变量,如下所示:
gwt.output.dir=www
entry.point.class=otn.todo.todoapp
最后,在 ant 脚本中声明属性文件(此处为 build.properties),如下所示:
您可以通过在任务的 context 菜单中选择 run target gwtcompile 直接启动该新的目标:
gwtcompile:
[java] output will be written into www\otn.todo.todoapp
[java] compilation succeeded
build successful
在 oc4j 中部署
编译完应用程序之后,在 oc4j 下部署它只需创建一个适当的部署配置文件。如果您按照前面描述的步骤进行操作,您应该已经具有一个默认的部署配置文件。如果没有,只需选择 file > new...> deployment profiles > war file,创建一个新的配置文件。
使用您的配置,一切都应该正常工作。但是,如果您遇到任何问题,请查看以下常见的错误:
- 在 project properties 的 project content > web application 中,html root directory 应该是应用程序的 www 目录(在该目录中,gwt 将对应用程序进行编译以便在托管模式下运行)。
- 在部署配置文件的 file groups > web-inf/lib > contributors 中,应该添加 gwt-user.jar。该 jar 文件包括 j2ee 规范中的 javax.servlet 程序包。这在本例中没有引起任何问题;但是,您通常不应将这些类部署在 web 应用程序中,因为它们会引起麻烦。如果发生了这种情况,gwt 还提供有一个 gwt-servlet.jar 文件,它是没有 javax.servlet 程序包的 gwt-user.jar。
- web 应用程序的上下文根影响 gwt 的 rpc 机制。如果 gwt 客户端应用程序要与服务器通信(如“使用 rpc 进行客户端和服务器之间的数据交换”所述),它必须可以找到服务器。这就是我们已经讨论过的 endpoint.setserviceentrypoint("") 方法的目的。在本例中,我们将应用程序部署到了服务器的根上,这就是 gwt shell 默认的工作方式。但是,如果您将应用程序部署到 todoapp web 上下文(在部署配置文件的一般属性中),请将端点设置为 /todoapp/todolistbackupservice 而不要设置为 /todolistbackupservice。不用忘记该 url 还应正确映射到应用程序的 web.xml 文件中(如前所述)。
- 假设 oc4j 已在系统上正确安装,将应用程序部署到服务器是很简单的:只需右键单击部署配置文件,并选择 deploy to oc4j。
部署的应用程序有两部分:
- 客户端应用程序是一组先前编译的 html 和 javascript 文件。(请参见“编译客户端代码”部分。)oc4j 充当经典的 web 服务器,将这些文件传递给用户。
- 服务器端应用程序基本上是一个处理 rpc 通信的 servlet。在 oc4j 中部署后,该 servlet 可以访问诸如 ejb 或 jms 提供商等企业资源。
性能智能化为静态资源提供了高效服务;应用程序的主要性能瓶颈来源于客户端/服务器通信。但是由于有 oc4j,我们可以访问一些有趣的服务器端性能图形:
如该图形所显示的,在正常负载下(每秒几个请求),应用程序的服务器端部分在平均不到 4 ms 的时间内进行响应,这是很难得的结果。
posted on 2006-11-24 10:31
坏男孩 阅读(2164)
评论(2) 所属分类:
新知识学习