《基于Jazz技术构建企业级Web2.0应用》系列第一部分:数据模型设计与持久化
《基于Jazz技术构建企业级Web2.0应用》系列第二部分:服务层设计与实现
《基于Jazz技术构建企业级Web2.0应用》系列第三部分:利用Jazz Process实现业务流程定制
【IT168 专稿】
在本系列前三部分中,我们以PetStore为例先后介绍了如何使用Jazz技术开发一个企业级应用的数据模型层和服务层,在这一部分中我们将要介绍如何开发此应用的富客户端UI。
Jazz开发框架为我们提供了两种富客户端方式供选择,一种是基于Eclipse的RCP,另外一种是目前很流行的Web客户端。Jazz为这两种服客户端都提供良好的基础设施支持,开发人员基于扩展点就可以开发出适合自己工程的客户端程序。本文首先介绍Jazz Web UI Foundation, 然后以Pet Store为例介绍基于Jazz的Web UI开发流程。
Jazz Web UI Foundation
Jazz的技术实现是基于Eclipse的OSGi包机制之上,每个组件都有良好的模块化设计结构, 具有良好的可扩展性,Web UI也不例外。 下面我们就来看一下Jazz Web UI Foundation的结构。Jazz Web UI Foundation对应四个包:
· org.dojotoolkit
· net.jazz.ajax (Jazz Ajax Framework)
· com.ibm.team.repository.web
· com.ibm.team.process.web
它们之间的关系如图1所示:
图1. Jazz Web UI Foundation 结构图
目前在业界有许多的Ajax Framework,Jazz采用的是Dojo Toolkit。 Dojo Toolkit在Jazz里被打包成一个命名为org.dojotoolkit的OSGi包, 这样它就可以通过Eclipse的插件机制来发挥作用。其中,Dojo通过提供一系列APIs,为JavaScript提供模块化管理能力和类Java的面向对象编程能力,给我们提供了一种方便的对象创建与扩展机制。同时,Dojo为声明式创建Ajax widgets提供一个强大的机制--它使用简单的HTML模板,并为这些widgets添加统一的事件处理机制,既屏蔽了浏览器对事件处理方法的差别,又使得程序开发人员可以方便的对站点进行处理。
net.jazz.ajax就是Jazz Ajax Framework(即JAF),是Jazz Web Foundation的核心。它首先将OSGi模块化模型扩展到Ajax领域,通过严格定义的类加载模型,组件开发人员可以获得Eclipse SDK插件开发环境(PDE)工具的强有力支持。同时,OSGi框架允许开发人员定义扩展点和这些扩展点的扩展性,从而很容易的对UI在多个级别上进行扩展。JAF的模块化模型也为Ajax资源的动态优化提供保证,它通过分析每个JAF组件中的元数据和Ajax代码,在执行应用时自动的优化Ajax资源,并能够通过延迟加载机制,智能的决定加载那些必须的模块,既加快页面装载速度,又减少应用程序的内存消耗。另外,JAF中URI驱动框架也可以简化后退、前进和历史标签,在不牺牲这些基本浏览器功能的前提下,使得开发人员很容易的达到构造复杂Ajax应用的目的。开发人员也可以在应用程序的URI中简单的插入"?debug=true"就能启动JAF的调试模式。
从以上的介绍我们可以看出,在Jazz Web UI Foundation中,Dojo Toolkit贡献了丰富的widget可供UI程序员使用。而net.jazz.ajax提供强大的UI模块化开发能力,这样不同的模块开发人员就可以独立开发自己模块的UI,并且可以使用Eclipse的扩展点机制实现模块与模块之间UI的松耦合。这些对Java EE的项目开发是非常重要的。
通常UI开发人员除了关心自己所使用的编程框架外还会关心UI和应用后端的数据交互接口,下面我们就介绍一下Jazz的数据封装和解封装,让您熟悉一下UI端请求数据的方式和所消费的数据格式。
Jazz的数据封装和解封装
Jazz和许多的Java EE项目一样,采用分层模型。Web UI通过调用REST服务来使用数据。数据的传输和转换流程为:
1.REST服务将数据封装成DTO;
2.Jazz Ajax Framework服务器端部分将DTO数据封装成SOAP消息发送给客户端;
3.Jazz Ajax Framework客户端部分将数据从SOAP消息解封装并转换成JSON格式。
程序员可见并需要编程的有两部分:
1.在 REST服务中将数据封装成DTO。
DTO作为一种设计模式在Java EE中被广泛使用, 负责服务器端和客户端之间的交互,REST服务在接到客户端的方法请求之后做一系列的逻辑操作,然后返回DTO形式封装的数据。
2.在Web UI中使用JSON格式数据。
Jazz Ajax Framework提供com.ibm.team.repository.web.transport.TeamServerClient来发起Ajax请求,TeamServerClient在得到服务器的响应后将拿到的SOAP消息解封装并将消息体中的values转换成JSON供客户端使用。
Jazz的Web UI开发流程
下面我们就来介绍PetStore的Web UI组件开发流程。
创建Web插件
创建Web插件项目com.ibm.petstore.web,我们将在其中定义Web UI的各个页面并与REST服务交互。需要注意的是在创建插件的Wizard中不要勾选“Create a Java project”。
下面我们创建与PetStore Web UI相关的文件夹和文件。图2显示了创建完成的文件夹和文件:
图2. PetStore Web UI文件目录
· resources文件夹是webBundles扩展点将页面内容注册到web服务的缺省目录。
· ui文件夹是用来存放所有与页面相关的文件。
· client文件夹存放与REST服务交互的文件。
· Internal文件夹说明当前目录下没有public API 或者可重用的公用widget。
· page文件夹存放与子组件对照的web页面。
另外需要注意的是我们创建Dojo widgets,还需要创建“internal/templates”文件夹,用于存放与Dojo widget相关的HTML和CSS文件。
添加Web页面
接下来我们需要为Web插件添加展示和控制Web页面的各个文件。整个页面由三个子页面构成:Catalog,Seller和Search。
1.resources/ui/internal/page/PetStore.js
PetStore.js是负责展示与控制整个页面的Dojo widget。它负责添加页面上方的navigation toolbar和页面的主体部分,如清单1所示:
清单1.
dojo.provide("com.ibm.petstore.web.ui.internal.page.PetStore");
//Require the ajax widget needed to show the content
dojo.require("net.jazz.ajax.ui.PlatformUI");
dojo.require("net.jazz.ajax.ui.PageContainer");
//Require the repository widget needed to show the navigation toolbar
dojo.require("com.ibm.team.repository.web.ui.internal.Navbar");
dojo.require("com.ibm.team.repository.web.client.internal.RepositoryClient");
dojo.require("com.ibm.team.repository.web.ui.internal._JazzApplication");
(function(){
dojo.declare("com.ibm.petstore.web.ui.internal.page.PetStore",
com.ibm.team.repository.web.ui.internal._JazzApplication, {
init: function(){
this.initUI();
//function in _JazzApplication, to start the whole page
this.start();
},
initUI: function(){
//create main content area
var container = document.createElement("div");
container.id = "outer-container";
var pageArea = new net.jazz.ajax.ui.PageContainer();
pageArea.domNode.insertBefore((
new com.ibm.team.repository.web.ui.internal.Navbar()).domNode, pageArea.domNode.firstChild);
container.appendChild(pageArea.domNode);
//add main content area to workbench
var workbenchRootNode = net.jazz.ajax.ui.PlatformUI.getWorkbench().rootNode();
workbenchRootNode.appendChild(container);
}
});
})();
其中,dojo.provide和 dojo.require是Dojo定义的APIs,负责JavaScript的模块化管理。Jazz Ajax Framework的优化器通过这个模块化管理系统了解各个模块之间的依赖关系,从而达到动态最优化的目的。
2.resources/ui/internal/page/templates/Catalog.html,resources/ui/internal/page/templates /Search.html,resources/ui/internal/page/templates /Seller.html
这三个文件分别对应三个子页面Dojo widget的模板。我们拿Catalog.js为例说明,如清单2所示:
清单2.
<div class="com-ibm-petstore-web-catalog">
<table class="table">
<tbody>
<tr>
<td width="25%">
<div dojoAttachPoint="_categories"/>
</td>
<td>
<div dojoAttachPoint="_detail"/>
</td>
</tr>
</tbody>
</table>
<div class="copyright">
Copyright(c) 2008 IBM
</div>
</div>
下面我们详细介绍其中的内容:
最外层的Div定义了当前的widget。我们通过class属性为此div元素添加CSS信息,其目的有两个:首先它限定了当前widget风格的范围,从而不会与其他widget产生命名空间的冲突;其次当开发人员使用浏览器调试工具检查DOM节点时,有利于当前widget的识别。如清单3所示:
清单3.
<div class="com-ibm-petstore-web-catalog">
table定义了一个表单节点。class属性指定了table元素的风格,其定义在相应的CSS文件中。而dojoAttachPoint属性是Dojo的专用属性,能被Dojo toolkit识别,其目的是使该widget相应的JavaScript文件能直接访问该节点,如清单4所示:
清单4.
<table class="table">
…
<div dojoAttachPoint="_categories"/>
…
<div dojoAttachPoint="_detail"/>
…
</table>
3.resources/ui/internal/page/Catalog.css,resources/ui/internal/page/Search.css,resources/ui/internal/page/Seller.css
这三个文件分别对应三个子页面widget的风格。我们拿Catalog.css为例,上面的Catalog.html中有两个class属性,因此其CSS文件如清单5所示:
清单5.
.com-ibm-petstore-web-catalog{
}
.com-ibm-petstore-web-catalog .table {
width: 50%;
height: 500px;
text-align: center;
}
.com-ibm-petstore-web-catalog .copyright {
text-align: center;
color: blue;
}
其中,.com-ibm-petstore-web-catalog定义了此文件的命名空间,而table和copyright则是具体风格的定义。
4.resources/ui/internal/page/Catalog.js,resources/ui/internal/page/Search.js,resources/ui/internal/page/Seller.js
这三个文件分别对应三个子页面的Dojo widget, 它们通过扩展点的方式自动加载Web应用。我们拿Catalog.js为例说明,如清单6所示:
清单6.
dojo.provide("com.ibm.petstore.web.ui.internal.page.Catalog");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.require("net.jazz.ajax.ui.PlatformUI");
dojo.require("com.ibm.team.repository.web.transport.ServiceResponseHandler");
dojo.require("com.ibm.petstore.web.client.PetStoreClient");
dojo.require("com.ibm.petstore.web.ui.internal.widget.CategoryItem");
dojo.require("com.ibm.petstore.web.ui.internal.widget.CategoryDetail");
(function(){
//local variable, using in current widget namespace
var ServiceResponseHandler = com.ibm.team.repository.web.transport.ServiceResponseHandler;
var PetStoreClient = com.ibm.petstore.web.client.PetStoreClient;
dojo.declare("com.ibm.petstore.web.ui.internal.page.Catalog", [dijit._Widget, dijit._Templated], {
templatePath: dojo.moduleUrl("com.ibm.petstore.web", "ui/internal/page/templates/Catalog.html"),
//inner variable
detailPart: null,
categories: [],
items: [],
// initialization
postCreate: function(){
…
},
catalogAction: function(){
…
}
});
})();
其中dojo.provide声明了此widget的命名空间,它允许其他Dojo对象访问此widget。dojo.require用来通知Dojo去加载其他的类文件。例如当前widget需要加载一些Dojo widgets和用户自定义的widgets。
清单7.
(function() {
...
})();
清单7是Jazz Web UI中的编码惯例。它为当前widget创建单独的命名空间。
清单8.
dojo.declare("com.ibm.petstore.web.ui.internal.page.Catalog", [dijit._Widget, dijit._Templated], {
templatePath: dojo.moduleUrl("com.ibm.petstore.web", "ui/internal/page/templates/Catalog.html"),
});
如清单8所示,dojo.declare声明了一个widget。第一个参数是widget的名字,其前缀必须与其在resources结构中的位置一致。第二个参数定义了widget的基类。第三个参数由一对{}包围,是此widget的具体实现,也称作widget的原型。
清单9.
postCreate: function(){
var ActionRegistry = net.jazz.ajax.ui.PlatformUI.getWorkbench().getActionRegistry();
ActionRegistry.registerAction( "com.ibm.petstore.web.catalogAction", this, "catalogAction");
this.detailPart = new com.ibm.petstore.web.ui.internal.widget.CategoryDetail();
}
如清单9所示,postCreate是Dojo widget的初始方法。在此它注册成为一个action,对应下文中扩展点的声明,其具体实现方法是catalogAction。而new com.ibm.petstore.web.ui.internal.widget.CategoryDetail 创建了一个新的Dojo widget对象。
清单10.
catalogAction: function(){
var responseHandler = new ServiceResponseHandler(this, "_success", "_error");
PetStoreClient.getAllCategoryDTOs(responseHandler, {});
},
_success: function(categories) {
this.categories=categories;
dojox.data.dom.removeChildren(this._categories);
…
this._categories.appendChild(item.domNode);
},
_error: function(e) {
…
}
如清单10所示,catalogAction是action的具体实现方法,它负责调用REST服务,并将结果反映在Web UI中。开发人员也可以使用Dojo 提供的统一事件处理方法使得用户与站点进行交互。
5.将页面添加到Jazz Web UI扩展点中,如清单11所示:
清单11.
<plugin>
<extension point="net.jazz.ajax.webBundles">
<prerequisites>
<requiredWebBundle id="net.jazz.ajax"/>
<requiredWebBundle id="com.ibm.team.repository.web"/>
<requiredWebBundle id="com.ibm.team.process.web"/>
</prerequisites>
</extension>
<extension id="PetStore" point="net.jazz.ajax.applications">
<application alias="/web/PetStore" jsclass="com.ibm.petstore.web.ui.internal.page.PetStore"/>
</extension>
<extension point="net.jazz.ajax.pages">
<page id="com.ibm.petstore.web.Catalog"
widget="com.ibm.petstore.web.ui.internal.page.Catalog"
name="Catalog"
defaultAction="com.ibm.petstore.web.catalogAction">
<applicationScope id="com.ibm.petstore.web.PetStore"/>
<action id="com.ibm.petstore.web.catalogAction"/>
</page>
</extension>
<extension point="net.jazz.ajax.pages">
<page id="com.ibm.petstore.web.Search"
widget="com.ibm.petstore.web.ui.internal.page.Search"
name="Search"
defaultAction="com.ibm.petstore.web.searchAction">
<applicationScope id="com.ibm.petstore.web.PetStore"/>
<action id="com.ibm.petstore.web.searchAction"/>
</page>
</extension>
<extension point="net.jazz.ajax.pages">
<page id="com.ibm.petstore.web.Seller"
widget="com.ibm.petstore.web.ui.internal.page.Seller"
name="Seller"
defaultAction="com.ibm.petstore.web.sellerAction">
<applicationScope id="com.ibm.petstore.web.PetStore"/>
<action id="com.ibm.petstore.web.sellerAction"/>
</page>
</extension>
</plugin>
现在我们更新plugin.xml,将上述页面添加到Jazz Web UI中。此插件定义了五个扩展:
· net.jazz.ajax.webBundles通知Equinox将当前插件中的资源加载到web命名空间中,并依赖于prerequisites中定义的Jazz Web基础构造。需要注意的是,如果当前插件是一个Web插件,要在此声明net.jazz.ajax包作为依赖项
· net.jazz.ajax.applications定义当前Web应用的URI上下文信息application alias以及此 应用的实现类。其实现类是上文介绍的com.ibm.petstore.web.ui.internal.page.PetStore。
· net.jazz.ajax.pages定义了具体的一个页面。当前插件中有三个页面:Catalog,Search和Seller。每个页面有唯一的id和显示在UI中的name,并声明其页面的具体实现widget。 applicationScope定义了当前页面的作用范围。action则与widget中具体的实现方法相对应,即3.4节中介绍的postCreate方法中,我们将com.ibm.petstore.web.catalogAction与catalogAction实现相关联。
调用REST服务
在Jazz架构模型中Jazz Web Client通过调用Rest Service和后端进行通信,其实质还是使用Ajax异步调用的方式,只不过Jazz做了一层封装,使得异步请求的代码书写更加简洁和通用。Jazz Web UI 通过TeamServerClient接口调用REST服务。例如上文Catalog.js的catalogAction方法调用PetStoreClient来处理REST请求,如果请求成功,则执行_success方法;否则执行 _error方法。PetStoreClient.js是TeamServerClient接口的具体实现如清单12所示:
清单12.
dojo.provide("com.ibm.petstore.web.client.internal.PetStoreClient");
dojo.require("com.ibm.team.repository.web.transport.ServiceRequest");
dojo.require("com.ibm.team.repository.web.transport.TeamServerClient");
(function() {
var ServiceRequest = com.ibm.team.repository.web.transport.ServiceRequest;
var TeamServerClient = com.ibm.team.repository.web.transport.TeamServerClient;
com.ibm.petstore.web.client.internal.PetStoreClient = {
PETSTORE_SERVICE_URI: "com.ibm.petstore.common.service.rest.IPetStoreRestService",
_invokeService: function(responseHandler, method, appArgs){
var actualArgs= {};
if (appArgs) {
var jsonArgs= dojo.json.serialize(appArgs);
actualArgs.jsonString= jsonArgs;
if(appArgs.category) {
actualArgs.category= appArgs.category;
}
}
var serviceRequest = new ServiceRequest(this.PETSTORE_SERVICE_URI, method, appArgs);
TeamServerClient.invokeService(serviceRequest, responseHandler);
},
getAllCategoryDTOs: function(serviceResponseHandler, params){
this._invokeService(serviceResponseHandler, "getAllCategoryDTOs", params);
}
};
})();
PETSTORE_SERVICE_URI声明了将要调用的REST服务。请求参数是JavaScript对象。REST服务执行异步调用,将返回结果通过response handler传递给服务请求者。
测试执行
在Run->Run…中创建一个Jetty launcher,或者直接使用附录工程代码中的PetStore -Server Start.launch文件。在浏览器中输入http://localhost:9080/jazz/web/PetStore访问PetStore页面。其中"web/PetStore"对应于net.jazz.ajax.applications扩展点中所定义的application alias字段。执行结果为:
图3. PetStore执行结果
总结
本文以PetStore的用户界面开发为例,介绍了如何利用Jazz Ajax Framework开发胖客户端 Web UI的过程。通过对JAF的详细阐述和主要开发流程的介绍,可以帮助用户在Jazz平台上快速构建基于Web 2.0的企业级Web应用。
参考资料
Writing a Jazz Web Hello World Page