引言
第 1 部分概述了如何在开发用于 IBM® WebSphere® Portal 的 portlet 时使用状态模式。第 2 部分介绍了一个使用状态模式的样本 portlet 的具体实现。这个样本 portlet 为用户维护一个联系方式列表。这个联系方式列表保存在一个数据库中,且该 portlet 允许用户个别查看这些联系方式或者在一个列表中查看。在编辑模式下,开发者可以添加、删除或修改联系方式。在配置模式下,开发者可以设置数据库访问信息。
Portlet 开发指南和示例实现使您能够很好地理解 portlet API。然而,实现复杂的过程控制超出了 API 的范围。如果没有一种设计良好的方法来解决如何最佳地实现控制逻辑这个问题,那么您最终创建出的 portlet 代码中有相当大一部分只能用于简单地满足用户的请求。
这些代码不仅是重复的,它们还使 portlet 更难读懂,因为您需要费很大力气去理解控制逻辑以了解 portlet 正在进行的实际工作。控制逻辑同样易于出错,因为它在 portlet 方法中依赖于字符串名称匹配以使事件与侦听器绑定、使侦听器操作与行为绑定。它还会使业务逻辑变得很复杂,因为控制功能的实现可能类似于业务逻辑。
为了解决控制逻辑的这些问题,您可以把应用程序看成是 portlet 操作和状态的一个集合。那么,在给定了定义明确的状态过渡方法后,您就可以删除 portlet 应用程序中麻烦的控制逻辑代码。本系列文章讨论了如何将这种方式应用于 portlet 开发的状态管理以及如何创建一个可重用的状态模式以使这个问题得到一般的处理。您将发现在 portlet 中应用了状态模式后,portlet 实现的结构将变得更简洁。
在第 2 部分中,您将实现功能齐全的 portlet,特别着重于状态模式实现。本文讨论了如何用 WebSphere Studio Application Developer(以下简称 Application Developer)创建 portlet。然而,您不用 Application Developer 开发环境也可以创建 portlet。本文还简要提及了其他的 portlet 要求(如数据库访问、异常处理以及配置参数)。
下面是 portlet 代码的先决条件:
- WebSphere Application Server 版本 4.0
- WebSphere Portal for Multiplatforms 版本 4.1
- 一个保存联系方式条目的数据库(例如 DB2®)
快速回顾状态模式
StateManagerPortlet 是主 portlet 类;您一般在其中编写所有特定于 portlet 的代码。然而,在这个实现中,这个类与 portlet 无关。它充当一个分派器(dispatcher),支持驻留了 portlet 代码的操作类和状态类。实现操作接口的类将实现一个 actionPerformed 方法。该方法负责执行实现操作请求所要求的功能所必需的任何操作。实现状态接口的类将实现一个 perform 方法。StateManagerPortlet 的 doView、doEdit、doHelp 或 doConfigure 方法调用该方法,且该方法还包含通常驻留在这些方法中的代码。同样,这段代码也是特定于它所驻留的状态类的,因此就避免了所有额外的控制逻辑混乱。最后,StateURI 提供一个定制标记供 JSP 使用。JSP 将把 HTML 页面上的一个链接与一个操作类实例关联起来。
应用状态模式会使实现更简洁。而且,当您在 portlet 的各个模式之间切换时,状态总是被记住的。有了这种模式,您就可以轻松地确定您想要何时以及在何处从源检索数据,还可以确定何时应该从高速缓存检索数据。由于在门户页面刷新时不调用 actionPerformed 方法,所以您可以将数据访问代码放在操作状态中并在那里高速缓存它们。状态类可以使用高速缓存中的数据以避免刷新门户页面时多次存取数据。
联系方式列表 portlet
我们在引言中提到过,您将使用一个为用户维护联系方式列表(简单的地址簿)的 portlet 的实现。这个 portlet 允许用户个别查看这些联系方式或者在一个列表中查看。同样,在编辑模式下,开发者可以添加、删除或修改联系方式。在配置模式下,开发者可以设置数据库访问信息。
从可视角度看,联系方式列表 portlet 的实现可以有下面几个视图(页面):
- 主视图— 显示联系方式列表,有一个选项用于选择一个联系方式以获取更多信息
- 详细信息视图— 显示被选中的联系方式的信息
- 主编辑视图— 显示联系方式列表,有几个用于添加、删除或修改的选项
- 添加联系方式视图— 显示一个表单以获取要添加的联系方式信息
- 修改联系方式视图— 显示一个类似的表单,插入了现有的数据以供修改
在这个案例中,您没有用来删除条目的显式页面。其行为是这样的:用户可以从主编辑页面中择一个联系方式条目来删除。将没有确认页面或执行成功页面。发生条目删除处理时,主编辑页面将被刷新。如果出错,那么将显示一条适当的消息。
假定您想让那些对 portlet 有查看权限的用户可以使用主视图和详细信息视图,让那些对 portlet 有编辑权限的用户可以使用添加、编辑和删除功能。在这个例子中,将用 doView portlet 方法控制主视图页面和详细信息视图页面。在编辑模式下,portlet 可以使用其余的页面。
建立数据库
首先,创建一个数据库表以保存联系方式列表数据。要创建表,请在命令行上运行下面的 SQL 命令(替换您选择的模式名称,但表名称是固定的)。您可以为这张表创建一个新数据库(数据空间),也可以将这张表添加到一个现有的数据库中。如果使用 DB2,请将下面的命令复制到命令中心(Command Center),然后在建立了一个到适当的数据库的连接后从命令中心运行该命令:
CREATE TABLE "DB2ADMIN"."CONTACTS" ("OWNER" VARCHAR(64) NOT NULL, "OID" VARCHAR(64) NOT NULL PRIMARY KEY, "FIRST_NAME" VARCHAR(32) NOT NULL, "LAST_NAME" VARCHAR(32) NOT NULL , "EMAIL" VARCHAR(128) NOT NULL, "TITLE" VARCHAR(64), "COMPANY" VARCHAR(64), "BUS_PHONE" VARCHAR(32), "MOBILE_PHONE" VARCHAR(32), "FAX_PHONE" VARCHAR(32), "WEB" VARCHAR(128), "ADDRESS1" VARCHAR(32), "ADDRESS2" VARCHAR(32), "CITY" VARCHAR(32), "STATE" VARCHAR(2), "ZIP" VARCHAR(10), "COUNTRY" VARCHAR(32) )
现在,您可以将一个条目添加到联系方式列表表中,这样您开发 porlet 时就可以更轻松地对它进行测试了。您可以执行下面的 SQL 命令以创建一个新条目(如果您打算用一个用户标识而非 wpsadmin
登录到门户中去进行测试,那么请在下面的 SQL 语句中用该用户标识代替 wpsadmin)。
insert into contacts (owner, oid, first_name, last_name, email, company, mobile_phone) values ('wpsadmin', '01', 'Tim', 'Hanis', 'hanistt@{域名已经过期}', 'IBM', '919-247-4098')
接下来,在 WebSphere Application Server 中为数据库(您在其中创建了 Contacts 表)创建一个数据源。在 Application Server Administrative Console 中选择 Resources => JDBC Providers,然后展开适当的 JDBC Driver。右键单击 Data Sources,选择 New。输入数据源名称作为 Name 的值。输入数据库名、用户标识和密码。然后,测试连接。
图 1. WebSphere Application Server Administrative Console
建立开发环境
您将用 WebSphere Studio Application Developer 创建 portlet。在 Application Developer 中,下面的 JAR 文件在构建路径上必须是可用的,以使 portlet 代码能成功编译。这一建立步骤只在开发时需要,因为当 portlet 应用程序被部署到 WebSphere Portal 环境时,所有这些 JAR 文件都是可用的。
在 Application Developer 中创建一个 Web 项目。如果您正在使用随 WebSphere Portal 提供的 Application Developer 的 Portlet 插件,那么您可以创建一个 portlet 应用程序项目来替代。使用 portlet 插件创建一个 portlet 应用程序项目将有助于建立构建类路径。在这两种情况下(或者如果使用的是另一种开发环境),请确保下面的 JAR 文件位于构建类路径上:
- WAS_PLUGINDIR/lib/j2ee.jar
- WAS_PLUGINDIR/lib/webcontainer.jar
- WAS_PLUGINDIR/lib/ivjejb35.jar
- WAS_PLUGINDIR/lib/websphere.jar
- WAS_PLUGINDIR//lib/cm.jar
- WPS_PLUGINDIR/portlet-api.jar
- WPS_PLUGINDIR/wpsportlets.jar
- WPS_PLUGINDIR/wps.jar
如果您正在使用安装了 Portal 插件的 Application Developer,那么在下面的路径上就有这些 JAR 文件:
- WAS_PLUGINDIR = <wsad_root>\plugins\com.ibm.etools.websphere.runtime\lib
- WPS_PLUGINDIR = <wsad_root>\plugins\com.ibm.wps
如果没有的话,您还可以在 <was_root>\lib\ 和 <wps_root>\lib\app 中找到这些文件。
开始实现
请考虑一下组成该 portlet 的状态和操作。同样,将操作看作您想为 portlet 实现的某种用户级别的功能或行为。例如,您可能想让一个操作添加一个联系方式或显示一个特定的视图。状态(state)是指用户操作完成后 portlet 的状况(condition),它通常有一个可视组件。例如,添加一个新联系方式的操作可能会使 portlet 的状态处于编辑模式,同时显示联系方式的主列表。
现在,考虑一下您想怎样让 portlet 看上去是可视的。在这个示例中,您想要一个列出您已创建的联系方式的主视图页面。该页面应该允许用户选择其中一个联系方式并获取关于这个人的附加的详细信息。
您还应该能够在列表中添加、删除和修改联系方式。您需要一个同样列出了联系方式的主编辑页面,但这次它允许您删除或修改一个选择。要进行删除,您不需要实现另一个视图;如果删除了一个选择,您仍是停留在原来的主编辑页面上,只是联系方式列表被正确刷新。如果您选择一个联系方式来修改,您将被带到一个详细信息页面,在此您可以更新这个联系方式条目的任何属性。当您完成修改时,您将被带回到主编辑页面。类似地,在主编辑页面上,您可以添加一个新的联系方式。您将被带到另一个详细信息页面,在此您可以输入并保存一个联系方式条目的属性。同样,保存之后,您将被带回到主编辑页面。
因此,您将有下面这些操作:
- 显示所有联系方式的主列表视图
- 显示被选中的联系方式的详细信息视图
- 显示联系方式的编辑视图列表
- 显示用于添加联系方式的视图
- 添加联系方式
- 显示用于修改联系方式的视图
- 修改联系方式
- 删除联系方式
执行这些操作会产生下列状态之一:
- 主视图
- 详细信息视图
- 主编辑视图
- 添加联系方式视图
- 修改联系方式视图
现在,您可以确定此应用程序的状态过渡。这些过渡是通过将适当的操作应用于当前状态实现的,这就导致应用程序要进入另一个特定的状态。对 portlet 有了这一层理解后,您就可以用状态模式创建应用程序了。
图 2. 此应用程序的状态过渡
请考虑一下所有的一切是如何一起工作的。首先,只看一下 portlet 的一部分。回顾一下影响您查看主联系方式列表的能力的操作和状态,然后选择一个具体的条目并查看其详细信息。
首先,您需要理解主 portlet 类都做什么。这个类(即 StateManagerPortlet)如下所示;您可以看到它基本上有两个方法,这两个方法做了大部分工作。在这个例子中,这个类的工作包括将当前处理分派给适当的操作类或状态类。您可以看到,actionPerformed 方法调用了操作类,因此,这些操作类被作为一个用户操作的结果调用,例如单击一个链接。当调用 portlet 类 service 方法时,状态类 performView 方法被调用。
请记住,这个逻辑流从 UI 上的一个用户操作开始。例如,用户单击主列表视图上一个具体的联系方式来检索这个被选中项的详细信息。该逻辑流怎样从此处开始?
UI 的定义如下所示:用户操作(单击一个联系方式条目)有一个链接,该链接是通过一个锚标记上相关联的 HREF 或 form 标记上的一个 action 定义的。无论这是以何种方式实现的,单击操作都会调用一个对 URI 的 HTTP 请求。当开发者为主视图的 UI 构建 JSP 时,他或她将链接与一个 PortletURI 相关联并使用标准的 addAction API 添加将一个实现用户操作行为的具体操作类添加到 PortletURI。在这个示例中,开发者创建了一个名为 DetailActionView 的操作类,它负责显示所选择的联系方式的详细信息视图。接着,您必须将这个类添加到 PortletURI。然后,当用户单击期望的联系方式时,StateManagerPortlet 类可以从事件对象获取操作类(使用标准的 getAction API),然后将这些类分派给它的 actionPerformed 方法。
下面的代码是 StateManagerPortlet 类的功能实现。这是一个很简洁的实现且非常普通。下面的代码还说明了这个类的 service 方法,我们将对其进行简要讨论。
清单 1. StateManagerPortlet 类
public class StateManagerPortlet extends AbstractPortlet implements ActionListener { public void service (PortletRequest request, PortletResponse response) throws PortletException, IOException { // Get the portlet state object from session, if not there get the // initial state for the mode. State nextState = (State) session.getAttribute(request.getMode().toString()); if (nextState == null) nextState = InitialStateManager.getInitialState(request.getMode()); // Dispatch to state nextState.performView(request, response, getPortletConfig().getContext()); } public void actionPerformed(ActionEvent event) throws PortletException { // Execute the perform action method for the event PortletRequest request = event.getRequest(); Action action = (Action) event.getAction(); PortletContext portletContext = getPortletConfig().getContext(); action.actionPerformed(request, portletContext); action.setState(request); } }
那么,操作类能为这个功能做些什么?操作类首先必须实现接口 PortletAction,这样您才可以将其添加到 PortletURI。您还需要为操作类实现一些公共功能;抽象操作类实现了 PortletAction。操作类将继承抽象操作类。下面提供了抽象操作类。它有一个带有两个方法的简洁的接口,这两个方法是为要实现的子类定义的。actionPerformed 方法是 StateManagerPortlet 类在进行分派时调用的类。StateManagerPortlet 还调用 setState 方法,该方法确保操作类实现设置了作为这个操作的过渡结果的状态。
抽象类也有一个 setState 方法,操作类可以调用该方法来处理状态类的设置,这样 StateManagerPortlet 就知道从何处获取它了。抽象操作类还有一个方便的方法,用于获取 PortletLog 以便对跟踪结果和消息进行日志记录。
清单 2. Action 类
public abstract class Action implements PortletAction { public abstract void actionPerformed(PortletRequest request, PortletContext portletContext) throws AIMException; public abstract void setState(PortletRequest request) throws AIMException; public void setState(PortletRequest request, State nextState) throws AIMException { PortletSession session = request.getPortletSession(); session.setAttribute(request.getMode().toString(), nextState); } public PortletLog getLog () { return PortletLogImpl.getInstance(); } }
在这个示例实现中,您有一个 MainViewAction 类,它继承了抽象操作类并且实现了 actionPerformed 方法和 setState 方法。对于 MainViewAction,actionPerformed 方法中不需要应用程序做任何工作,因此方法的实现为空(empty)。setState 方法将 MainViewState 设置为您过渡到的状态(使用抽象操作类的 setState 方法)。
清单 3. MainViewAction 类
public class MainViewAction extends Action { /** * Perform action request */ public void actionPerformed(PortletRequest request, PortletContext portletContext) throws AIMException { } /** * Set the next Portlet state */ public void setState(PortletRequest request) throws AIMException { setState(request, new MainViewState()); } }
当 actionPerformed 完成处理时,控制返回到门户容器以便其他的侦听器通知处理程序执行。然后,容器通过调用 StateManagerPortlet 的 service 方法继续 portlet 请求处理。
和 StateManagerPortlet 代码中一样,service 方法试图检索以前的操作类设置的状态类。如果这是第一次调用 portlet,状态可能未被设置;没有操作事件被调用过,因此 actionPerformed 方法也没被调用过。在这种情况下,您从助手类 InitialStateManager 获取初始状态。InitialStateManager 有一个名为 getInitialState 的单独的方法,它根据 portlet 的当前模式返回一个状态对象。在示例中,最初调用 portlet 时,InitialStateManager 将为 portlet 的缺省查看模式返回一个 MainViewState 实例。
您可能已经轻松地在 StateManagerPortlet 类中对这些初始值进行硬编码了,但这个抽象让 StateManagerPortlet 类是一般的,并且让实现者选择这些值是被动态读取还是被硬编码。
状态接口有一个所有的状态类都需要实现的单独的方法(performView 方法)。StateManagerPortlet 的 service 方法调用该方法。
清单 4. State 接口
public interface State { public void performView(PortletRequest request, PortletResponse response, PortletContext portletContext) throws IOException, PortletException, AIMException; }
然后,MainViewState 类实现 performView 方法。它负责从数据库中介者获取联系方式列表作为一个名为 Contacts 的模型对象集合。接着,它将这个集合添加到请求对象以便 JSP 生成页面。JSP 被调用,页面生成。
清单 5. MainViewState 类
public class MainViewState implements State { // Static fields protected static final String viewListJsp = "/jsp/contacts/viewList.jsp"; /** * Contact list view */ public void performView(PortletRequest request, PortletResponse response, PortletContext portletContext) throws IOException, PortletException, AIMException { // Get the ContactList data, put it on the request object List contactList = ContactListBroker.getInstance().getContactList(); request.setAttribute("contactList", contactList); // Invoke the JSP to render portletContext.include(viewListJsp, request, response); } }
下面的表展示了用于生成主列表视图的大多数 JSP 代码。在此,请注意锚标记上的 HREF。这个锚标记允许用户单击一个联系方式条目,然后显示该联系方式的详细信息视图。
这个标记的 HREF 使用下面的定制标记:
<stateURI:createURI state="com.ibm.aim.actions.DetailViewAction"/>
这个标记用一个具体的操作类实例创建了一个 URI,这个操作类是使用 PortletURI addAction 方法添加的。现在,您有了一个向后引用 portlet 的 URI,您可以为这个 portlet 获取到适当操作的合适的过渡,然后,您让操作类提供到合适的状态类的适当的过渡。如果这个视图中的用户可以使用更多的操作,那么您可以用正确的操作类创建适当的 portletURI,这样,状态过渡过程中将继续正确的处理。 清单 10中提供了这个定制标记的代码。
清单 6. 主列表视图 JSP
<jsp:useBean id="contactList" class="java.util.List" scope="request" /> <%@ page import="com.ibm.aim.beans.Contact" %> <form method="POST" name="<portletAPI:encodeNamespace value="viewList"/>" > <input type="hidden" name="selectedContact"> <table border="0" width="85%" align="center" cellspacing="0" cellpadding="0" > <thead> <tr> <td class="wpsTableHead">Name</td> <td class="wpsTableHead">Company</td> <td class="wpsTableHead">Email</td> <td class="wpsTableHead">Phone</td> </tr> </thead> <tbody> <% for (int _ind=0; _ind<contactList.size(); _ind++ ) { Contact contact = (Contact) contactList.get(_ind); %> <tr> <td class="wpsPortletSmText" valign="top" align="left"> <a style="text-decoration: none; color:black; font-family: sans-serif; font-size: 8pt;" onMouseOver="this.style.color='red'" onMouseOut="this.style.color='#000000'" href="javascript:document.<portletAPI:encodeNamespace value="viewList"/>.action='<stateURI:createURI state="com.ibm.aim.actions.DetailViewAction"/>'; document.<portletAPI:encodeNamespace value="viewList"/>.selectedContact.value= '<%=contact.getOid()%>'; document.<portletAPI:encodeNamespace value="viewList"/>.submit()"> <%= contact.getFirstName() %> <%= contact.getLastName() %> </a> </td> <td class="wpsPortletSmText" valign="top" align="left"> <%= contact.getCompany() %> </td> <td class="wpsPortletSmText" valign="top" align="left"> <%= contact.getEmail() %> </td> <td class="wpsPortletSmText" valign="top" align="left"> <%= contact.getMobilePhone() %> </td> </tr> <% } %> </tbody> </table> </form>
图 3. 主列表视图
继续这个示例,用户选择一个具体的联系方式条目以获取详细的信息。StateManagerPortlet actionPerformed 方法被调用。该方法从事件对象处获取 DetailViewAction 类,并且调用它的 actionPeformed 方法,然后调用它的 setState 方法。actionPerformed 方法获取所选择的联系方式的用户标识,调用中介者以获取用户标识实例化的联系方式对象,并将模型对象放在会话上供 State 类使用。
这种方法在这里为什么有效以及为什么将它放在会话上,而不仅仅直接在下一个状态类中获取 OID 和对象?
关键在于,在门户的页面刷新时发生了两件事情:表单数据未被重新提交,portlet 的 actionPeformed 方法未被调用(但是 portlet 的 service 方法将被调用)。
因此,如果您试图获取所选择的联系方式的用户标识作为请求对象参数,那么您在页面刷新时将找不到它。所以,为使 portlet 能正确运行,必须在刚开始时就检索一下数据元素,然后将其存储在某个地方,这样在随后的页面刷新时它才可以被引用。由于 actionPerformed 方法曾被调用,所以这就是存放这段代码的好地方。
另外,由于在随后的页面刷新时未调用 actionPerformed 方法,所以您可以将访问数据库中的源数据的代码放在此处,这样,您就不必在门户页面每次被刷新时都试图检索联系方式信息了。最好进行一次这样的操作,并且保持随后的刷新可以使用该代码。当然,您返回到数据源进行数据刷新的时间和频率取决于 portlet 的要求以及数据的本性。在这种情况下,数据不是动态的,当 portlet 页面被刷新时,应该对其进行高速缓存
最后,DetailViewState 被设置为这个 portlet 的下一个状态:
清单 7. DetailViewAction 类
public class DetailViewAction extends Action { /** * Perform action request */ public void actionPerformed(PortletRequest request, PortletContext portletContext) throws AIMException { // Get the userid String userid = request.getUser().getUserID(); // Get the selected contact oid String oid = request.getParameter("selectedContact"); // Get the Contact element and put it on session Contact contact = ContactListBroker.getInstance().getContact(userid, oid); PortletSession session = request.getPortletSession(); session.setAttribute("contact", contact); } /** * Set the next Portlet state */ public void setState(PortletRequest request) throws AIMException { setState(request, new DetailViewState()); } }
DetailViewState 仅从会话获取联系方式对象并将它放在请求对象上供 JSP 使用(这是为了 JSP bean 处理的一致性)。
清单 8. DetailViewState 类
public class DetailViewState implements State { // Static fields protected static final String viewDetailJsp = "/jsp/contacts/viewDetail.jsp"; /** * Perform request */ public void performView(PortletRequest request, PortletResponse response, PortletContext portletContext) throws IOException, PortletException, AIMException { // Get the contact from session and set it on the request object // (yes, strictly not needed since the jsp has access to the session) PortletSession session = request.getPortletSession(); Contact contact = (Contact)session.getAttribute("contact"); request.setAttribute("contact", contact); // Invoke the JSP to render portletContext.include(viewDetailJsp, request, response); } }
MainViewAction 的完整实现与本文先前显示的略有不同。如果联系方式对象存在于会话上,那么它通过删除联系方式对象来彻底删除会话数据。
这个视图的 JSP 如下所示。请注意,它还使用 createURI 定制标记:
<stateURI:createURI state="com.ibm.aim.actions.MainViewAction?>
在这个例子中,当用户单击 OK 按钮时,处理通过返回主视图继续进行。
清单 9. 联系方式详细信息视图 JSP
<jsp:useBean id="contact" class="com.ibm.aim.beans.Contact" scope="request" /> <br> <p class="wpsPortletHead"> Contact Detail Information <Br> <p> <table align="center" border=3 width=400> <!--- Table Rows ---> <tr> <td class="wpsTableHead"> First Name </td> <td class="wpsPortletSmText"> <%= contact.getFirstName() %> </td> </tr> <tr> <td class="wpsTableHead"> Last Name </td> <td class="wpsPortletSmText"> <%= contact.getLastName() %> </td> </tr> ……………… More here ………………….. </table> <form method="post" name="<portletAPI:encodeNamespace value="viewContactDetail"/>"> <table cellspacing="4" cellpadding="0" border="0" width="85%" align="center"> <tr> <td height="1" class="wpsAdminHeadSeparator" colspan="3"></td> </tr> <tr> <td> <a style="text-decoration: none;" href="javascript:document.<portletAPI:encodeNamespace value="viewContactDetail"/> Action='<stateURI:createURI state="com.ibm.aim.actions.MainViewAction"/>'; document.<portletAPI:encodeNamespace value="viewContactDetail"/>Submit()" > <IMG src='<%= wpsBaseURL %>/images/admin/header_ok.gif' align="absmiddle" title="Ok" border="0"/> <span class="wpsTaskIconText">OK</span> </a> </td> </tr> </table> </form>
图 4. 详细信息视图
这就完成了 portlet 的视图部分的流程流。您可以看到,流逻辑以一种组织良好的方式进行,您在 portlet 中尚未引入冗长的控制代码。
portlet 的编辑模式和配置模式将使用同一种方法实现。主编辑视图如下所示。
图 5. 编辑视图
public class StateURI extends TagSupport { private String state = ""; public void setState(String state) { this.state = state; } public int doStartTag() throws JspException { PortletResponse portletResponse = (PortletResponse) ThreadAttributesManager.getAttribute(Constants.PORTLET_RESPONSE); PortletURI portletURI = portletResponse.createURI(); Class anAction = Class.forName(state); Action action = (Action) anAction.newInstance(); portletURI.addAction(action); pageContext.getOut().print(portletURI.toString()); return SKIP_BODY; } public int doEndTag() throws JspException { return EVAL_PAGE; } }
将 portlet 状态和操作封装在一起
该应用程序的其余部分仍遵循这个模式。在查看模式下,流程控制逻辑完全按照与编辑模式下相同的路径流动。
- 编辑视图的初始状态是通过 InitialStateManager 类提供的。
- 调用 State 类的 performView 方法,获取它所需的模型对象,然后将它们传递给 JSP。
- JSP 将一个或多个 PortletURI 分配给 UI 上的操作,并将每个 PortletURI 与一个具体的操作类相关联。
- 当用户单击一个链接时,操作类的 actionPerformed 方法被调用。操作逻辑被执行,适当的状态被设置。
- 当用户浏览整个 portlet 时,操作以这种方式继续。
持久性中介者
访问数据库中的持久性数据需要额外的 portlet 组件。这个完整的实现有大量支持这种数据访问的类。AbstractBroker 类提供一般的 JDBC 数据库访问功能。这个类还获取正确的数据源、获取数据连接并执行一个预编译的语句。
ContactListBroker 类继承了 AbstractBroker。它实现特定于 portlet 要求的数据访问方法,如 getContact 方法和 getContactList 方法。这些方法还可用于保存和删除一个联系方式条目。ContactListBroker 被作为单子程序管理,类实例被初始化,带有 BrokerParms 的一个实现作为参数。
BrokerParms 是一个接口,它为初始化中介者所需的值指定了访问方法。AbstractBroker 需要知道数据源名称以及运行 JNDI 命名服务的服务器的主机名。这个接口中定义了 getDatasourceName 和 getContextProviderServer 这两个方法。
只要该实现返回正确的数据源和服务器主机名,您就可以选择如何获取这个接口需要的值。您可以将值硬编码到一个实现类中,或者 portlet 可以动态配置设置。在本文中,您实现了一个动态设置方法。
Portlet 设置
为了维护 portlet 必需的配置设置,您在 portlet 中实现了一个配置模式以修改和保存 portlet 部署描述符(portlet.xml)参数。配置模式功能的实现与状态模式类似。您实现了 ConfigureAction 类和 ConfigureState 类,它们提供显示配置参数的功能。它允许用户更新和保存值。
您还已经实现了一个 PortletParameters 类。这个类保存当前的 portlet 配置参数值。这个类还实现了 BrokerParms;用这个类初始化 ContactListBroker。因此,portlet 配置参数中的两个参数指定了数据源名称和 JNDI 服务器主机名。
您还可以指定附加的配置值。您有一个名为 debugTrace 的附加设置。这个布尔值确定如何把生成的错误消息写回到 portlet。请参阅下一部分, 异常处理。
异常处理
您已经为我们的 portlet 实现了大量的异常类。基本类名为 AIMException。您可以创建一个 AIMException 实例,带有一条指出错误条件的消息。AIMWrapperException 是 AIMException 的子类。您可以用一条错误消息和一个异常作为参数来初始化这个子类。您可以用 AIMWrapperException 封装其他抛出的异常,以便在 StateManagerPortlet service 方法中高效地修改显示行为和管理异常处理。
AIMMessageException 是一个特殊的异常,它允许您在终止处理时向 portlet 生成一条报告性消息或错误消息。例如,您可以抛出一个 AIMMessage 异常,用一条消息指出用户必须先登录。
actionPerformed 方法或 setState 方法中抛出的异常被捕获,并且被延迟到 service 方法。管理延迟的方法是捕获这些方法中抛出的所有异常并将异常(通常封装在一个 AIMWrapperException 中)放在请求对象上。当调用 StateManagerPortlet service 方法时,它首先查找有没有延迟的异常,如果找到了,就从此处重新抛出并处理这些异常。
因此,所有的 portlet 应用程序异常都是在 StateManagerPortlet service 方法中处理的。如果发现了异常,在以下两种情况中调用异常方法:(a) 异常消息将在 portlet 中被显示或者 (b) 异常消息以及相关联的堆栈跟踪信息将被显示。将堆栈跟踪信息放在 portlet 外有助于在开发时进行调试。由于您不想向实际应用的用户显示这种级别的详细信息,所以该行为是可配置的。这是 Portlet 设置部分中所讨论的 debugTrace 参数。
通过捕获和显示 service 方法(不仅是 AIMExceptions)中的所有异常,您也可以获取 portlet 中关于 JSP 错误的更多有用的调试信息。这有助于减少普通消息(如“This portlet is unavailable”)的数量。如果持续存在该问题,请联系门户管理员。否则,您将在 JSP 文件中接收到问题的清楚的说明。
实现类总结
包类描述com.ibm.aim.actionsAction抽象操作类MainViewAction处理显示主视图(联系方式列表)的请求DetailViewAction处理显示联系方式的详细信息视图的请求MainEditAction处理显示主编辑视图的请求AddViewAction处理显示添加联系方式视图的请求AddAction处理实际添加一条联系方式的请求ModifyViewAction处理显示修改联系方式视图的请求ModifyAction处理实际保存一个修改的联系方式的请求DeleteAction处理实际删除一条联系方式的请求ConfigureAction处理保存一个已更改的配置设置的请求com.ibm.aim.statesState基本状态接口MainViewState生成主联系方式列表视图DetailViewState生成联系方式的详细信息视图MainEditState生成主编辑视图AddViewState生成添加联系方式视图ModifyViewState生成修改联系方式视图ConfigureState生成配置参数设置视图com.ibm.aim.portletStateManagerPortletPortlet 类PortletInitializer初始化 portlet 实例PortletParameters处理要求 portlet 配置参数设置的请求InitialStateManager处理初始 portlet 状态的请求com.ibm.aim.persistenceAbstractBroker基本中介者类BrokerParms需要初始化中介者所需参数的接口ContactListBroker特定于 Portlet 的中介者功能com.ibm.aim.beansContact代表一个联系方式条目对象ContactHelper提供添加、删除和修改一个联系方式条目的公共功能com.ibm.aim.tagsStateURI生成带有适当的 Action 类的定制 JSP 标记com.ibm.aim.utilitiesAIMException基本异常类
结束语
作为企业门户应用程序的首选平台,WebSphere Portal 继续向前发展。拥有定义明确的用于开发 portlet 的框架和模式正变得更加重要。
本文讨论了用于 portlet 开发的状态模式的实现,它提供了在 portlet 中管理复杂应用程序页面流的能力。状态模式提供高效的处理和简洁的代码,有助于开发者轻松地调试、维护和增强应用程序。下面的 下载ZIP 文件中提供了这个样本的完整实现。
下载




















