级别: 初级
陈隽伟 (mailto:chenjunw@cn.ibm.com?subject=Faces Portlet开发框架初体验)IBM中国软件实验室
2004 年 7 月 01 日
本文介绍了最新版WSAD 5.1.2上基于 JSF技术的Faces Portlet 框架的特色,从 MVC模式角度与基本的 portlet 进行了比较,并进一步深入分析了Faces portlet 开发过程中的关键类的具体含义与功能。
简介
最新发布的 IBM WebSphere Studio Application Developer 和 IBM WebSphere Studio Site Developer 5.1.2 版支持新的行业标准,可以简化针对丰富 Web 用户界面、商务逻辑(business logic)和交互式门户的开发。全新的快速应用程序开发(RAD)和代码生成工具是WebSphere Studio 的一大亮点,可以帮助实现项目流程自动化,简化Java开发,从而加快整个团队的开发速度。
使用新的WebSphere Studio产品来构建丰富的用户界面、数据连接以及面向Web应用程序的商务逻辑,Java开发人员可以立即获得工作效率的提升。WebSphere Studio 支持最近获得Java Community ProcessSM批准的 JavaServer? Faces (JSF)标准和Java Community ProcessSM推荐的Service Data Objects(SDO)标准。WebSphere Studio使用这些标准来最大限度地减少构建数据驱动的Web应用程序和丰富的Web用户界面所需要的Java编码和手动操作。JSF和SDO的可视化工具使开发人员可以方便地将JSF用户界面(UI)组件拖放到页面,点击即可连接到关系型数据库的数据源。
因此,借助WSAD V5.1.2开发工具的帮助,我们可以使用JSF相关的技术来实现终端用户自己开发的小型系统,更大规模的门户应用,或者是规模庞大的企业系统。
此外,该版本的WebSphere Studio Application Developer 还包括 IBM WebSphere Portal 测试环境,新的可视化 portlet 开发工具,并支持新行业标准 portlet Application Programming Interface (API),可确保 portlet 互操作性和便携性。构建 portlet 的可视化工具可简化门户开发,而与 JSF 的集成使其可以轻易地整合 portlet 中的丰富的用户界面(UI)组件和 Web 窗体。与过去相比,这大大简化了整个门户开发过程,速度也大幅提高,但对相应开发人员技能却没有很高的要求。 这就是最新的Faces Portlet开发框架。
本文是有关Faces Portlet开发的系列文章的第一篇,在这些文章中作者会和开发人员谈论有关Faces Portlet开发方面很多有趣的话题。
目标读者:希望读者阅读本文之前,能够对MVC,portlet和JSF有一定的了解,相关的资料可以在参考资料中找到。
出发之前,先来准备我们的开发测试环境吧。
Faces Portlet开发测试环境的准备:我们使用的开发工具是WSAD V5.1.2,同时还需要安装IBM提供的Portal Toolkit V5.0.2.2。
- WSAD V5.1.2
您可以从下面这个链接下载到WSAD V5.1.2的试用版,请 下载安装。注意:这里一般不能从WSAD的老版本通过更新管理器来升级您的WSAD版本,最方便可靠的办法是重新安装这个新的版本。
- 运行环境:
WebSphere Portal V5.0.2或者WebSphere Portal Express V5.0.2 都可以用来发布Faces Portlet应用。
好了,做完上面这一切,你就可以开始着手开发Faces Portlet应用了。
(图1)
如上图(图1),WSAD V5.1.2提供了四种不同的创建portlet应用的向导。开发人员以前最常用的是Basic portlet这个向导,这里我们就先来比较一下Basic portlet和Faces portlet有哪些区别和类似之处。
传统上Basic portlet的开发过程
http://www-128.ibm.com/developerworks/cn/websphere/library/techarticles/0403_lynn/0403_lynn.html从上面这篇文章,你可以了解到以前开发一个最简单的portlet的一般步骤,最基本的步骤如下:
1. 创建目录结构:使用Basic portlet的创建向导,WSAD会帮你自动创建目录结构。
2. 准备JSP文件: 准备好需要显示的所有JSP页面。
3. 创建Portlet: 创建Portlet类,它通常是继承于PortletAdapter的,需要实现的最重要的方法是actionPerformed()和doView()。
4. 准备配置文件: 打包发布之前需要维护web.xml以及portlet.xml,通常WSAD会帮你完成这一切。
5. 打包,发布这个应用:将你的应用导出成WAR包,然后在WebSphere Portal Server上安装,并且把它装载到某个页面中,你就可以看到它的外观了。
Faces Portlet的主要优点
Faces portlet脱胎于Basic portlet,并在其基础上将JSF技术整合进来,充分吸收了JSF的很多优点,包括丰富的用户界面控件,便捷的事件处理和页面导航等特性,进一步降低了开发portlet应用的门槛。如果能够充分利用WSAD V5.1.2提供的工具支持的话,开发人员几乎感觉不到是在开发portlet应用,而只是在开发基于JSF技术的动态Web应用。
正是由于项目中有了下面这些Jar包的支持,Faces portlet才能正常工作。
jsf-api.jar,jsf-ibm.jar,jsf-impl.jar,jsf-portlet.jar
下面,我们再来从实现MVC模式的角度来比较Basic portlet和Faces portlet的异同:
模型(Model)-视图(View)-控制(Controller)模式
(图2)
如图2描述,MVC模式是由下面三个重要的部分组成的:
Model层实现系统的业务逻辑,通常可以用Java Bean或是EJB来实现。
View层用于与用户的交互,通常用JSP来实现。
Controller层是Model和View之间沟通的桥梁,它用来分派用户的请求并且选择恰当的视图用于显示,同时,它还将用户的输入映射为模型层相应可执行的操作。
Basic portlet和MVC模式的对应关系
(图3)
如图3,在Basic portlet框架中,后台的business bean或是EJB则封装了应用的业务逻辑,对应于Model层;JSP页面对应于 View层,它们是用户所看到的内容;而控制器则是一个继承于PortletAdapter类的Portlet类,其通过actionPerformed()方法和doView()方法来完成接收用户请求,分派调用处理函数,选择适当的视图跳转等控制工作。
下面两个代码片断给出了Portlet类中actionPerformed() 和 doView()的样例。
代码片断1:actionPerformed()通常负责接收用户事件,再分派至相应的处理函数。
public void actionPerformed(ActionEvent event) throws PortletException {
// ActionEvent handler
String actionString = event.getActionString();
// Add action string handler here
PortletRequest request = event.getRequest();
PortletSession session = request.getPortletSession();
actionBean.setSession(session);
actionBean.setRequest(request);
try {
Class actionClass = actionBean.getClass();
Method method = actionClass.getMethod("on_" + actionString, null);
method.invoke(actionBean, null);
} catch (Exception e) {
}
}
|
代码片断2:doView()则通常负责根据后台的业务逻辑选择指定相应的页面视图。
public void doView(PortletRequest request, PortletResponse response)
throws PortletException, IOException {
PortletSession session = request.getPortletSession();
String JSPFile = (String) session.getAttribute(JSPPAGENAME);
if (JSPFile == null) {
// First time enter into the portle
}
// Set actionURI
setActionURIs(request, response);
// Invoke the JSP to render
getPortletConfig().getContext().include(JSPFile, request, response);
}
|
Faces portlet中和mvc模式的对应关系
(图4)
如图4,后台同样是通过business bean或是EJB,封装了应用的业务逻辑,对应于Model层;
而View层则是采用了Faces JSP(这是一种特别的JSP,它的界面是由JSF用户界面控件组成的)。 相对而言,这里的控制层(Controller)较为复杂,每个Faces JSP页面都对应着一个Action Bean,负责处理该页面上所有的用户输入检验,事件处理,以及相关页面之间的跳转,这些都是Basic portlet中portlet类中的actionPerformed()和doView()所要做的。 看上去好像以前那个控制器已经被抛弃了,但事实却不是这样,正如图4中Controller右侧的虚线框表示的那样,它被巧妙的隐藏了起来,开发人员不会感觉到它的存在,但它却没有离开,依旧在后面控制着一切。
比较了Basic portlet和Faces portlet在MVC模式的实现上的异同之后,该来深入了解一下Faces portlet了。
深入了解faces portlet项目
1. 目录结构
通过Faces portlet wizard新建一个Portlet项目,分析一下目录结构
(图5)
上面这张图是一个初始的Portlet项目的目录图。
先来看WebContent
- \WebContent
这里会存放所有的JSP页面,但是当页面数量很多的时候这并不是一个好办法。也许可以将页面分类分别存放在一些子目录里。
- \WebContent\META-INF
这里通常会有一个METAFEST.MF文件,存放着一些和项目相关的元数据。我们并不需要关心它里面有些什么。
- \WebContent\theme
这里则是用来放置Portlet应用相关的页面样式等,诸如css文件,这通常是美工会关心的地方。
- \WebContent\WEB-INF
这里是整个项目的核心重地,存放了项目所有重要的配置文件。
- \WebContent\WEB-INF\lib
这里存放着和Faces Portlet开发框架密切相关的一些Jar包。
- \WebContent\WEB-INF\classes
这里则存放了编译好的一些Java类文件。
接下来,再来看Java Resources
- \Java Resources\pagecode
这里存放着和WebContent中每个JSP页面对应的Java类,用来处理JSP页面的后台动作,而所有这些都是继承PageCodeBase这样一个基类。
之前我们提到过,每个Faces JSP页面都会有个Action Bean和其相对应,它们应该在Java Resources中,那JSP文件是如何与这个Action Bean关联起来的呢?
答案在这里,在每个Faces JSP页面中都有一段注释,如下面这个例子:
<%-- jsf:codeBehind language="java" location="/JavaSource/pagecode/Master.java" --%><%-- /jsf:codeBehind --%>
通过这段注释,这个Faces JSP页面就和/JavaSource/pagecode/Master.java 这个Action Bean建立了对应关系。注:编辑这段注释,你可以改变这种对应关系,但似乎在WSAD的编辑器中是没有办法直接修改的,你可以用记事本直接在磁盘上修改它。
2. PageCodeBase.java (附件1中是这个类的完整内容)
这个类是构建一切Action Bean的基础,它封装了JSF技术的一些最基本的功能。下面就抽取了其中最重要的几个函数,分别讲解它们的作用。
代码片断3:
public PageCodeBase() {
facesContext = FacesContext.getCurrentInstance();
requestScope =
(Map) facesContext
.getApplication()
.createValueBinding("#{requestScope}")
.getValue(facesContext);
sessionScope =
(Map) facesContext
.getApplication()
.createValueBinding("#{sessionScope}")
.getValue(facesContext);
applicationScope =
(Map) facesContext
.getApplication()
.createValueBinding("#{applicationScope}")
.getValue(facesContext);
requestParam =
(Map) facesContext
.getApplication()
.createValueBinding("#{param}")
.getValue(facesContext);
}
|
这个函数是PageCodeBase这个类的构造器,它负责获取当前FacesContext的实例之后,将FacesContext之间传递数据的4个Map结构中的数据提取出来,分别是#{requestScope},"#{sessionScope}","#{applicationScope}","#{param}"它们分别表示在request范围内,在session范围内,在application范围内共享的变量以及页面之间需要传递的参数。
代码片断4:
protected void gotoPage(String pageName) {
if (pageName != null) {
UIViewRoot newView =
facesContext.getApplication().getViewHandler().createView(
facesContext,
pageName);
facesContext.setViewRoot(newView);
}
}
|
这个函数实现了基本的页面跳转的功能,通过gotoPage(String pageName)这个函数,页面可以重定向到新的页面。
代码片断5:
public static UIComponent findComponent(UIComponent base, String id) {
// Is the "base" component itself the match we are looking for?
if (id.equals(base.getId())) {
return base;
}
// Search through our facets and children
UIComponent kid = null;
UIComponent result = null;
Iterator kids = base.getFacetsAndChildren();
while (kids.hasNext() && (result == null)) {
kid = (UIComponent) kids.next();
if (id.equals(kid.getId())) {
result = kid;
break;
}
result = findComponent(kid, id);
if (result != null) {
break;
}
}
return result;
}
|
这个函数一般情况下不会调用,在JSF中,每个页面中包含的所有的控件根据相互之间的嵌套关系组成一个控件树,这个函数的作用就是在控件树上查找并返回指定的控件实例。
代码片断6:
protected void putTreeAttribute(String key, Object value) {
getFacesContext().getViewRoot().getAttributes().put(key, value);
}
protected Object getTreeAttribute(String key) {
return getFacesContext().getViewRoot().getAttributes().get(key);
}
|
页面之间很多数据都是以该页面所对应的控件树的某些属性值的形式来存储的, 这两个函数可以让开发人员在代码中存取一些需要特殊维护的数据。
代码片断7:
protected Object resolveExpression(String expression) {
Object value = null;
if ((expression.indexOf("#{") != -1)
&& (expression.indexOf("#{") < expression.indexOf('}'))) {
value =
getFacesContext().getApplication().createValueBinding(
expression).getValue(
getFacesContext());
} else {
value = expression;
}
return value;
}
|
为什么在Faces JSP页面中出现的所有变量都是以#{beanname.fieldname}来存在呢,奥秘就在这个函数,Faces Portlet框架内部解析变量时就默认采用了这样一种格式。
代码片断8:
protected void resolveParams(
Map paramMap,
String[] argNames,
String[] argValues,
String cacheMapKey) {
Object rawCache = getTreeAttribute(cacheMapKey);
Map cache = Collections.EMPTY_MAP;
if (rawCache instanceof Map) {
cache = (Map) rawCache;
}
for (int i = 0; i < argNames.length; i++) {
Object result = resolveExpression(argValues[i]);
if (result == null) {
result = cache.get(argNames[i]);
}
paramMap.put(argNames[i], result);
}
putTreeAttribute(cacheMapKey, paramMap);
}
|
这个函数是用来解析页面之间传递的参数的,通常不需要开发人员来调用。
代码片断9:
protected static String getRealPath(String relPath) {
String path = relPath;
try {
URL url =
FacesContext
.getCurrentInstance()
.getExternalContext()
.getResource(
relPath);
if (url != null) {
path = url.getPath();
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
return path;
}
|
这个函数用来返回当前的FacesContext所对应Faces JSP页面的绝对路径,在某些场合下非常有用。
代码片断10:
protected void logException(Throwable throwable) {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
throwable.printStackTrace(printWriter);
log(stringWriter.toString());
}
|
这个函数在调试程序时非常有用,它将发生的异常的StackTrace重定向到日志文件中而不是简单的打印在控制台上。
3. faces-config.xml
这个配置文件存储了几乎所有和Faces portlet密切相关的配置信息。从 http://java.sun.com/dtd/web-facesconfig_1_0.dtd你可以找到它的DTD的具体内容。
不可能解释所有的属性和标签,这里通过一个具体的例子(附件2中是它的完整内容)来熟悉最常用的一些属性。
代码片断11:
<lifecycle>
<phase-listener>com.ibm.faces.webapp.ValueResourcePhaseListener
</phase-listener>
</lifecycle>
|
这里定义了在JSF的页面生命周期每个阶段状态的监听器,默认是不需要修改的,这里使用的是IBM提供的实现:com.ibm.faces.webapp.ValueResourcePhaseListener
代码片断12:
<factory>
<faces-context-factory>
com.ibm.faces.context.WPPortletFacesContextFactoryImpl
</faces-context-factory>
</factory>
|
这里则定义了和FacesContext相关的类工厂实现,这里默认也是IBM提供的实现:com.ibm.faces.context.WPPortletFacesContextFactoryImpl
代码片断13:
<application>
<locale-config>
<default-locale>zh_CN</default-locale>
<supported-locale>en_US</supported-locale>
<supported-locale>de_DE</supported-locale>
<supported-locale>fr_FR</supported-locale>
</locale-config>
<message-bundle>messagebundle</message-bundle>
</application>
|
这里定义了这个Portlet应用支持的locale的种类,特别指出了默认支持的locale,并且还指定了应用中存储各种类型的message所需要的message bundle的名称。
代码片断14:
<managed-bean>
<managed-bean-name>pc_PTestView</managed-bean-name>
<managed-bean-class>pagecode.PTestView</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>businessbean</managed-bean-name>
<managed-bean-class>pagecode.businessbean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
|
在Faces portlet应用中,任何使用的Java Bean都会在配置文件中注册为一个managed bean,包括变量引用时使用的名称,具体的实现类名,以及它们的使用范围(request, session, application)
代码片断15:
<navigation-rule>
<display-name>navigation rule sample</display-name>
<from-view-id>/master.jsp</from-view-id>
<navigation-case>
<from-action>#{pc_Master.doDetailsBtnAction}</from-action>
<from-outcome>success</from-outcome>
<to-view-id>/details.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-action>#{pc_Master.doDetailsBtnAction}</from-action>
<from-outcome>failure</from-outcome>
<to-view-id>/homepage.jsp</to-view-id>
</navigation-case>
</navigation-rule>
|
这段也是常见的配置信息,它定义了portlet应用中各个页面之间的跳转关系。 from-view-id 定义了这条跳转规则中的源页面 <navigation-case></navigation-case> 则通过这个标签定义了针对一个源页面的各种跳转情况。 <from-action> Faces portlet中的跳转规则是根据某个事件处理函数的返回值来判断的,这里就定义了具体事件处理函数的名称。 <from-outcome> 这里则定义了这种跳转情形所对应的事件处理函数的返回值。<to-view-id> 这里定义了在事件处理函数返回相应的返回值后跳转的目标页面。
小结:
Faces Portlet是一个不错的Portlet应用开发框架,值得去体验一下。如果你过去有过开发portlet应用的经验,这些宝贵的经验会让你事半功倍。 希望这篇文章能够让你对Faces portlet印象深刻。
参考资料
- http://publib.boulder.ibm.com/infocenter/wsphelp/index.jspInformation Center of WebSphere Studio and WebSphere Application Server
- http://publib.boulder.ibm.com/pvc/wp/502/ent/en/InfoCenter/index.htmlInformation Center of WebSphere Portal Server v5.0.2
- WSAD V5.1.2的联机帮助,它会随WSAD一起安装。
- http://www.ibm.com/developerworks/cn/views/websphere/tutorials.jsp?cv_doc_id=85218教程:创建基于 Web 的用户界面--使用 WebSphere Studio V5.1.1 来开发 JavaServer Faces 应用程序
- http://www-128.ibm.com/developerworks/cn/wsdd/library/techarticles/0403_lynn/0403_lynn.shtmlHello World -- WebSphere Portal V5 最简单的 portlet
- http://www.jcp.org/en/jsr/detail?id=127JSR127,这是JCP通过的JSF规范
- http://www.jsfcentral.com/非常不错的一个关于JSF的开发社区
- http://www-128.ibm.com/developerworks/cn/websphere/zones/studio/theme/studionewtech.htmlWSStudio新功能入门专题
- 在 http://www.google.com/上搜索关键字MVC,你可以很快找到很多关于MVC的材料。
关于作者
|