使用 Oracle JDeveloper 构建您的第一个 GWT Web 应用程序

阅读更多

使用 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”。

该方法分为三部分:

  1. 创建 Button 和 Label 小部件
  2. 创建 ClickListener 对象。该代码与您用 Swing 编写的内容很接近;如果您具有桌面 Java 背景则更容易理解。
  3. 在 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 进行编码。在服务器端,您必须编码出具有以下特征的类:
  1. 扩展 Google 的 com.google.gwt.user.server.rpc.RemoteServiceServlet 类(该类反过来会扩展 Java 的 javax.servlet.http.HttpServlet,有效使其成为 servlet)
  2. 实施步骤 1 中编写的接口
  3. 位于服务器程序包(本例为 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 直接启动该新的目标:

      Apache Ant Log 窗口将显示以下结果:

    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 的时间内进行响应,这是很难得的结果。

你可能感兴趣的:(GWT,Web,Oracle,企业应用,应用服务器)