本文说明了如何将一个为 IBM WebSphere Portal 专用 Portlet API 开发的 portlet 转换为使用 JSR 168 标准 portlet API 的 portlet。它描述了在执行这样的转换时存在的主要问题。如果您需要将一个 portlet 从 IBM Portlet API 转换到 JSR 168 API,本文为您提供了一个非常好的模型。
Java™ Portlet 规范(JSR 168)定义了本地的 portlet 如何接入到门户。我们需要这个标准的 portlet API,因为在过去这几年中,不同的门户供应商定义了不同的 API,但是它们没有一个是可以互操作的。创建这个标准的 API 是由 IBM 和 Sun 在 Java Community Process 内部共同努力实现的。该规范使本地 portlet 和门户之间可以实现互操作,提供给 portlet 开发人员一个简单的编程模型,提供了同其他 Java 技术的移植性和互操作性,并通过 J2EE 技术利用和调整了 portlet API。
在创建 JSR 168 以前,IBM 已经在 WebSphere Portal 里面提供了一个专有的 API。然而,随着 JSR 168 的出现,我们推荐 portlet 开发人员使用新的标准的 portlet API。将使用 IBM Portlet API 的 portlet 转换为使用 JSR 168 API,需要做几个方面的转换。在 portlet 描述符、JSP 和它们的标签以及主要的 portlet 方法上两者有很大的不同。它们有不同的方法参数和操作流,并且需要重新编写一些功能,因为两者之间的 API 并没有一一对应的关系。
本文描述了如何转换 WorldClock portlet,该 portlet 用 IBM 的 WebSphere Portal 开发,要将其从 IBM Portlet API 转换到 JSR 168 API。这个例子包括了当执行这样的 portlet 转换,并且将其应用到 WebSphere Portal version 5 及以上版本时应该注意的主要问题。请注意这里显示的代码片断没必要符合最佳实践;该转换的目的只不过是用最小的努力来转换 portlet。
对比 IBM WebSphere Portlet API 与 JSR 168 API
IBM Portlet API 和 JSR 168 API 在主要方面是相似的。IBM Portlet API 在功能上要丰富一些,因为它已经定义了好几年并且一直在加强。这就意味着将使用 IBM portlet API 的 portlet 转换到 JSR API 的 portlet 时,会出现一些困难,因为 JSR 168 API 可能并不支持原始 portlet 所支持的所有功能。现在 JSR 168 API 正在添加更多的功能,因此,到时困难将会越来越少。不过 IBM 为 JSR 168 portlet API 提供了扩展,以提供至少与传统的 IBM portlet API 相同的功能。
两个 API 之间主要的相似点和不同点概括如下。如果想要了解关于 portlet API 不同点更加详细的描述,参阅 比较 JSR 168 Java Portlet 规范和 IBM Portlet API
相似的 API 概念 | 不同的 API 概念 |
|
|
用来做转换范例的 WorldClock portlet 是一个简单的 portlet,实现了简单的功能,因此是转换的一个良好范例。该 portlet 允许用户设定他们的本地时区,在该时区内查看当前时间。用户同样可以在单独的时区内快速的搜索当前本地时间。该 portlet 依赖运行 WebSphere Portal 的服务器提供的时间来进行计算。
图1. 范例 WorldClock Portlet

![]() ![]() |
![]()
|
接下来的步骤描述了 WorldClock portlet 从 IBM Portlet API 向 JSR 168 API 一步步转换。为了对转换前后的代码不同有一个总体了解,我们提供了两种 WorldClock portlet 的范例源代码。该转换步骤分成以下四个部分。
每个部分都提供了原始 portlet 和转换后的 portlet 的代码对比。大多数例子同样链接到代码的完整范例,让您可以在 portlet 里面合适的方法上下文中查看代码。
当将 portlet 从 IBM Portlet API 转换到 JSR 168 API 时需要执行一些常见的步骤。这些步骤包含对 import 声明的更改和方法的重构和重命名。
IBM Portlet API | JSR 168 API | ||
|
|
引入的 IBM Portlet API 包包含了完整功能的 portlet 所需的所有接口和实现类。在 JSR 168 API 的包 javax.portlet.*;
中,这些必须的接口和类已经被打包在一起,例如,包含 portlet 的 action 请求和响应、rander 请求和响应、配置和其他所需的接口。参考 WebSphere Portal 5.0 的 Portlet API Javadoc 和 JSR 168 Portlet 规范 API,了解包内容的完整列表。
IBM Portlet API | JSR 168 API | ||
|
|
IBM Portlet API 的 PortletAdapter
提供了 Portlet
接口的默认实现。 ActionListener
接口是 Portlet
接口的一个补充。如果 portlet 中的对象想要获取 action 事件,除了 Portlet
接口以外还必须实现这个接口。
对于 JSR 168 portlet, GenericPortlet
类提供了对 Portlet
接口的默认实现。它提供了抽象子类来创建 portlet。 GenericPortlet
类允许您实现 processAction
方法,该方法处理 action 处理,其在 IBM portlet API 的 ActionListener
接口中提供。
这些代码是 WorldClock portlet 的 构造器的一部分。
IBM Portlet API | JSR 168 API | ||
|
|
init
方法都可以抛出
UnavailableException
异常。然而,在 JSR 168 情况下,当它不能成功执行其操作时,同样可以抛出通用异常
PortletException
。
IBM Portlet API | JSR 168 API | ||
|
|
doView
方法里面作为参数传递。
IBM 专有的 portlet API 并没有遵照 Model-View-Controller(MVC)模式。在 portlet 的 render 阶段期间,它的状态依旧可以改变。在 JSR 168 portlet API 中这是不正确的,它通过不同的请求(render 和 action )来采取不同的方式,不管 portlet 的状态是可以修改,还是 portlet 的内容正在被显示。JSR 168 种特定 portlet API 的 doView
方法表现 MVC 模式的 view 组件。Portlet 的内容可能提供到促使调用的 actionPerformed
方法的 action URL。Portlet 的状态只能通过 action 请求在该方法中更改。这部分表现 MVC 模式的 controller 部分。
这是 WorldClock portlet 的 doView
方法的一部分。
IBM Portlet API | JSR 168 API | ||
|
|
ActionListener
接口及其实现类来单独进行。每个 action 处理的标准实现已经在 JSR 168 的
GenericPortlet
类中提供。因为 JSR 168 API 对待 action 请求响应和 render 请求响应是不同的,
processAction
方法接收
ActionRequest
和
ActionResponse
对象作为参数。
下面这段代码是 WorldClock portlet 的 actionPerformed
方法的一部分。
这些是在将 portlet 从 IBM Portlet API 转换到 JSR 168 API 时的一些详细步骤。对于这些改变,您需要对这两种 API 有比较深的了解。这些在转换 WorldClock portlet 时所需的详细步骤将在本节中进行描述。
IBM Portlet API | JSR 168 API | ||
|
|
PortletSettings
对象提供了关于 portlet 的基本配置信息,然而每个 portlet 实例的个性信息是在
PortletData
对象中提供。在 JSR 168 API 里面,这些信息仅仅通过一个叫做
PortletPreferences
的对象提供。当使用 preference 属性代替 portlet 设置或者 portlet 数据时,需要考虑两个主要的不同点:
PortletPreferences
的 preference 属性允许空值。因此,如果没有设定值的话是否通过指定一个返回的默认值,JSR 168 API 是区别对待的。PortletPreferences
的reset
方法移除或重置 preference 值为可用的预定义的值。IBM Portlet API 通过PortletData
对象的remove
方法来实现这一功能。
下面是 WorldClock portlet 的 doView
方法的一部分。
IBM 特定 API | JSR 168 API | ||
|
|
在 IBM Portlet API 里面,在 PortletContext
中通过直接使用 include
方法包含了一个外部资源。在 JSR 168 API 中, PortletRequestDispatcher
对象是由 PortletContext
来提供;然后使用该对象的include
方法来包含资源。
点击 这里获取 WorldClock portlet 实现的代码范例。
IBM Portlet API | JSR 168 API | ||
|
|
PortletAction
对象需要创建和扩展。使用 JSR 168 API,
createActionURL
方法直接返回调用 action 方法的 URL。为了指定 action,必须设置一个参数。
下面是 WorldClock portlet 的 doEdit
方法的部分代码。
IBM Portlet API | JSR 168 API | ||
|
|
ModeModifier
来在事件处理中定义下一个 portlet 模式。使用 JSR 168 API,aciton 响应的
setPortletMode
方法用来更改到任何 portlet 模式。当在两种 portlet API 的转换期间,请记住 IBM Portlet API 的
createReturnURI
方法将 portlet 模式改回该 portlet 的上一个模式(通常将它从 EDIT 改为 VIEW 模式)。为了在 JSR 168 中具备相同的行为,
createReturnURI
调用需要由
RenderResponse.createActionURL
来代替。在该方法中调用
processAction
,该 portlet 的 VIEW 模式可以明确的设定在 action 响应中。
IBM Portlet API | JSR 168 API | ||
|
|
PortletContext
访问日志工具。然而,JSR 168 Portlet API 在 portlet 上下文本身提供了
log
方法,而不是使用
PortletContext.getLog
方法返回的
PortletLog
。
Portlet 部署描述符( portlet.xml
)用来描述 web 存档中打包的 portlet;除了 web 存档中必须存在的使其有效的 web 部署描述符( web.xml
)之外,还必须提供这个部署描述符。JSR 168 Portlet API 没有使用 web.xml
来存储 portlet 信息。因此 JSR 168 Portlet 应用程序的 web.xml
文件可能是空的。
这里有一些将 portlet 部署描述符从 IBM 特定 Portlet API 转换为 JSR 168 API 时需要执行的详细步骤。对于这些更改需要对两种 portlet 描述符有较深的理解。因为 portlet 部署描述符用非常不同的结构提供关于 portlet 的信息,这里仅列出 WorldClock portlet 的 portlet.xml
文件中呈现的一些不同之处。
IBM Portlet API | JSR 168 API | ||
|
|
portlet.xml
描述了一个 portlet 应用程序,如同 IBM portlet Document Type Definition(DTD)或者 JSR 168 portlet XML Schema Definition(XSD)中描述的那样。IBM Portlet API 将一个 portlet 应用程序的定义划分为一个
portlet-app
以及一个
concrete-portlet-app
,然而 JSR 168 API 仅仅定义了一个
portlet-app
。因此转换
portlet.xml
的第一个步骤是更改 portlet 应用程序标签,包含到 portlet 部署描述符的 XML Schema 定义的引用。
每个 portlet 通过 portlet 应用程序里面的 <portlet>
标签来定义;这在两种 API 中是相同的。
IBM Portlet API | JSR 168 API | ||
|
|
web.xml
文件中,通过和 servlet 一样的方式来指定。然而 JSR 168 API portlet 是它们自己的组件,并不是特殊的 servlet,该 portlet 的完整类名是在
portlet.xml
中的
<portlet-class>
标签中指定的。
IBM Portlet API | JSR 168 API | ||
|
|
<supports>
块中指定的。IBM Portlet API 通过
<markup>
标签来指定支持的标记语言;JSR 168 API 使用
<mime-type>
标签。该 portlet 模式中由 IBM 专有模式标签(例如,<view>,<edit>,<help>)定义的模式需要更改为 JSR 168 API 的
<portlet-mode>
标签。因为在 JSR 168 API 中还定义了 configure 模式作为一个选项,在
<custom-portlet-mode>
标签的
<portlet-mode>
标签中也必须定义这个 portlet 模式。
IBM Portlet API | JSR 168 API | ||
|
|
<portlet-info>
标签中。为了根据区域来支持不同的设置,该信息可以使用 JSR 168 特殊的关键字(例如,
javax.portlet.title
,
javax.portlet.short-title
以及
javax.portlet.keywords
)在一个资源包文件中指定。这些资源包必须在每个 portlet 定义的
<resource-bundle>
标签中指定。
两个版本的 WorldClock portlet 都包含四个 JSP 文件,用来显示内容。
BidiInclude.jsp | BidiInclude JSP 使对 BIDI 的支持更加容易。该 JSP 中定义的变量补充了 <bidi> 标签的内容。 |
WorldClockEdit.jsp | WorldClockEdit JSP 用来修改 WorldClock portlet 并且更改其设置或者首选项。 |
WorldClockHelp.jsp | WorldClockHelp JSP 呈现一个 JSP 来显示 WorldClock portlet 的帮助页面。 |
WorldClockView.jsp | WorldClockView JSP 用来显示 WorldClock portlet 的内容。 |
在 IBM 和标准 WorldClock portlet 之间,它们的 JSP 文件有一些不同。WorldClock portlet 使用的 JSP 文件,BidiInclude.jsp、WorldClockEdit.jsp 以及 WorldClockView.jsp 使用 Portlet API 指定的 JSP 标签。Portlet 标签库使 portlet 中包含的 JSP 文件可以直接访问 portlet 特定的元素,诸如 portlet 请求和响应。它同样允许 JSP 去访问 portlet 功能比如 portlet URL 的创建。在 JSP 里面,标签元素始终都是通过它定义的前缀来引用的(例如,portletAPI)。
JSP 通过以下的方式引用 IBM 特定的 portlet 标签:
<%@ taglib uri="/WEB-INF/tld/portlet.tld" prefix="portletAPI" %> |
.
JSR JSP 像这样来引用标签库:
<%@ taglib uri="http://java.sun.com/portlet" prefix="portletExt" %> |
WebSphere Portal 提供了额外的标签来供 JSR 168 portlet 中使用。为了使这些标签能够在 JSP 中使用,需要添加以下的指令:
<%@ taglib uri="/WEB-INF/tld/ibm-portlet-ext.tld" prefix="portletExt"%> |
<portletExt/>
通过以下方式使用:
<portletExt:bidi dir="rtl|ltr" locale=" locale" /> |
dir: 指明语言中的文本的正常方向。
- 对于 dir="rtl",仅仅当客户端的区域属于双向语言时,标签内容才能写入。
- 对于 dir="ltr",仅仅当客户端的区域不属于双向语言时,标签内容才能写入。
locale: 仅仅当语言属于 LocalizerService.properties
中定义的双向语言时,标签内容才能写入。
在 IBM WorldClock portlet 中已经提到的 JSP 使用了下面这些标签元素:
init | 实现该标签功能的类创建了可以在 JSP 中使用的以下四个变量:
<defineObjects> |
encodeNamespace | 实现该标签功能的类将给定的字符串值映射到这个 portlet 的命名空间。标签参数:
<namespace> |
text(不赞成) | 实现该标签功能的类向输出流中写入一个局部字符串。当没有发现资源包文件时,在 start 和 end 标签之间的文本将写入到输出流中。标签的参数是:
|
bidi | 实现该标签功能的类支持双向语言的文本。除了当显示从左向右的文本字符串(例如,URL、代码范例或者目录和文件名)时,双向语言一般都是从右相左读。 目前,没有相应的反映这一功能的 JSTL JSP 标签可用,但是提供了一个 IBM 扩展,如上所示。 |
encodeURI | 实现该标签功能的类创建 PortletResponse 对象,并且执行 encodeURI 方法。 以下 JSR 168 API 指定 portlet 标签提供了相同的行为: <actionURL> and <renderURL> . |
以下的标签元素已经在 JSR 168 WorldClock portlet 的 JSP 中提到:
defineObjects | defineObjects 标签必须在 JSP 中定义以下的变量:
这些变量必须引用存储在 JSP 的请求对象中的相同的 JSR 168 API 对象。使用
在使用 | |
actionURL | 该 portlet 的 actionURL 标签创建 URL ,并且必须指向当前的 portlet,必须用支持的参数来触发 action 请求。通过在actionURL start 和 end 标签之间包含 param 标签,参数可以添加到 URL 中。下面是为这个标签定义的非必须的属性:
| |
renderURL | 该 portlet 的 renderURL 标签创建一个 URL,该 URL 必须指向当前的 portlet,并且必须触发使用支持参数的 render 请求。参数可以通过在 renderURL start 和 end 标签之间包含 param 标签来添加。正如 actionURL 标签中列出的那样,这个标签也同样定义了相同的非必须属性。 | |
namespace | 这个标签为当前的 portlet 产生一个唯一的值。该标签应该用于 portlet 输出中指定的元素(例如 JavaScript 方法和变量)。该命名空间确保给定的名字唯一的与这个 portlet 联系在一起,并且避免同该 portlet 页面或者该页面其他 portlet 上的其他元素发生冲突。 | |
param | 这个标签定义了一个参数,可以添加到 actionURL 或者 renderURL 中。 param 标签不能包含任何主体内容。为这个标签定义了以下必须的属性:
|
对于标准 portlet 标签的更多的参考,请参考 The JSR 168 Portlet Specification。
范例 Standard WorldClockEdit JSP 代码片断
<%@ page session="false" buffer="none"%> <%@ page import="java.util.*,javax.portlet.*,com.ibm.wps.portlets.worldclock.WorldClockController" %> <%@ taglib uri="http://java.sun.com/portlet" prefix="portletAPI" %> <portletAPI:defineObjects/> <portletAPI:actionURL var="saveURI"> <portletAPI:param name="<%=WorldClockController.COMMAND%>" value="<%=WorldClockController.SAVE%>"/> </portletAPI:actionURL> ...... <!-- build table for edit screen --> <form name="WORLD_CLOCK_EDIT" action="<%=saveURI%>" method="POST"> ..... <table > <tr> <td> <label for="local_time_zone"><%= WorldClockController.getText ("text.local_time_zone", renderRequest.getLocale ()) %></label> </td> </tr> <tr> <td> <%-- on the next line width is for Netscape, style is for IE --%> <select id="local_time_zone" name="LocalTimeZone" size="1"> <% int selPos = -1; String localTimezone = (String)renderRequest.getAttribute("localTimezone"); if (localTimezone != null) { try { selPos = Integer.parseInt(localTimezone); } catch (NumberFormatException ex) { } } Vector timezoneVector = (Vector)renderRequest.getAttribute("timezoneVector"); for (int i=0; i < timezoneVector.size(); i++) { String id = (String)timezoneVector.elementAt(i); // exclude default selection if ( selPos != i ) { %> <option value="<%= i%>"><%= id %></option> <% } else { %> <option selected value="<%= i%><%= id %></option> <% } } %> </select> </td> </tr> </table> ..... </form> |
![]() ![]() |
![]()
|
本节比较了用 IBM Portlet API 编写的 WorldClock portlet 源代码,和转换后的 JSR 168 Portlet API 的 portlet 源代码,突出显示的代码指明了转换上下文中的改变。
使用 IBM Portlet API 的 WorldClock portlet 构造器 | 使用 JSR 168 API 的 WorldClock portlet 构造器 | ||
|
|
使用 IBM Portlet API 的 WorldClock portlet doView 方法 | 使用 JSR 168 API 的 WorldClock portlet doView 方法 | ||
|
|
使用 IBM Portlet API 的 WorldClock portlet doEdit 方法 | 使用 JSR 168 API 的 WorldClock portlet doEdit 方法 | ||
|
|
使用 IBM Portlet API 的WorldClock portlet doHelp 方法 | 使用 JSR 168 API 的 WorldClock portlet doHelp 方法 | ||
|
|
使用 IBM Portlet API 的 WorldClock portlet actionPerformed 方法 | 使用 JSR 168 API 的 WorldClock portlet actionPerformed 方法 | ||
|
|
![]() ![]() |
![]()
|
本文示范了一个简单的方法,将用 IBM Portlet API 开发的 portlet 转换为遵循 JSR 168 的 portlet。虽然没有包括两种 API 之间的所有不同,但这个转换的例子涉及了这种转换的一些通常的问题,并没有涵盖 JSR 168 portlet 编程模型的最佳实践。这个例子使开发人员能够更好的理解两种 API 的主要不同,以及如何改变功能性,将使用一种 API 的 portlet 转换到另一种。