第一部分 Portlet 2.0 新特性介绍
======================================================================
关于本系列
本系列文章专门针对具有 JSR 168 Portlet 开发基础,并且想了解 JSR 286 Portlet 新特性和开发流程的开发人员。在学习完本系列后,您将了解相对于JSR 168 Portlet,JSR 286 Portlet 究竟提供了哪些增强功能, 以及这些新增特性在实际开发中的应用。
第 1 部分将简单回顾 JSR168 Portlet, 并列出了 JSR 286 Portlet 的新增内容。
第 2 部分和第 3 部分将通过在 Apache Pluto 2.0 平台上开发和部署 Portlet 应用程序, 向读者介绍 JSR 286 Portlet 新特性的使用方法。
关于本文
本文假定读者熟知 JSR 168 Portlet,并对 J2EE 基本常识有一定了解。本文主要以理论的方式向读者介绍 JSR 286 Portlet 的以下新增特性:
资源服务
事件
共享呈现参数
Portlet 过滤器
Portlet 窗口
Portlet 和 JSR 168
Portlet 是部署在容器内用来生成动态内容的 Web 组件,与 servlet 类似,portlet 的整个生命周期从 init 到 destroy 的过程都在 portlet 容器中进行。Java Portlet Specification 对 portlet API、标准化用户数据、参数设置、portlet 请求以及响应、部署、打包以及安全等方面都做了详细的规定,以此来实现portlet 之间以及 portlet 与 portlet 容器之间的交互和协作。 Java PortletSpecification 1.0, 即 Java Specification Request(JSR)168 发布于 2003 年 10 月。
JSR 286 及其新特性
JSR 168 目前在业界受到广泛支持,而且它由开放源码支持。标准和产品的第一个版本存在一定的缺陷,仅支持最基本的用例,在功能上有一些限制。而且 Java Portlet Specification V1.0 也存在这种情况,因此,经过三年之后,大多数支持 Java Portlet Specification V1.0 的门户产品都提供一些附加扩展,以支持更高级的用例,这些附加的扩展造成了各个门户产品的标准不统一,彼此间的交互协作成了不可避免的问题。为了更好地规范 portlet 开发,以适应业界发展,并提供适应于最高级别用例的标准解决方案,从而为这些高级功能提供互操作性,在 2005 年 11 月开始了 Java Portlet Specification V2.0(称为 JSR 286)的开发,Java Portlet Specification V2.0目前已经进入 Finaldraft 的等待审批阶段,并计划在2008 年 3 月正式发布。JSR 286 最终草案兼容了 JSR 168,并完善了 JSR 168 的部分功能,并提供了诸多 JSR 168 所没有的新特性,例如资源服务、事件、portlet 过滤器、共享呈现参数及 portlet 窗口等。与 V1.0 类似,V2.0 也将基于 J2EE 1.4,因此可让 Portlet 使用 J2EE 1.4 增强(如 JSP 2.0)。下面是该新规范的一些主要功能及特性:
资源服务:一种新的通过 portlet 呈现资源的方式。
事件:通过发送事件和接收事件来实现 portlet 之间的通信。
Portlet 过滤器:与 servlet 过滤器类似,根据 Portlet 请求和响应动态的呈现内容的变换。存在以下四种类型的 portlet 过滤器:
Action 过滤器
Render 过滤器
Resource 过滤器
Event 过滤器
共享呈现参数:除了 portlet 私有的呈现参数之外,新增了可以在 portlet 之间共享的呈现参数。
Portlet 窗口:提供 portlet 窗口 ID 供 portlet 使用。
下面我们将对 JSR 286 所提供的这些新功能及其使用逐一做详细介绍。
资源服务
在 JSR 168 中,Portlet 服务于资源的方法只有两种:直接链接到资源,或者通过 Portlet 服务于资源。两种方法分别适用于不同目的的需要,各有优缺点。
直接链接对于所有 Portlet 状态都相同的静态资源非常有效,但对于其他用例效果却不太好,因为需要考虑来自 Portlet 上下文的信息。这样的示例包括基于 Portlet 模式、窗口状态、当前呈现参数或 Portlet 首选项呈现不同资源。
以一个 JSP 文件 test.jsp 为例,如果要访问该资源,可以直接通过超链接访问该文件,如清单 1 所示:
清单 1. 直接访问资源文件
<a href="<c:url value="/test.jsp" />">test.jsp</a>
或者通过 Servlet 转向,如清单 2 和清单 3 所示:
清单 2. 直接访问 Servlet
<a href="<c:url value="/testServlet" />">testServlet</a>
清单 3. Servlet 对资源文件的访问控制
public void service(ServletRequest request, ServletResponse response)
throws ServletException,IOException {
...
在此添加访问控制等业务逻辑代码
...
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/jsp/test.jsp");
rd.forward(request, response); //或者为 rd.include(request, response);
}
从清单 1、2、3 可以看到,直接链接到资源这种方式,无法访问到相关 Portlet 的信息,包括 Portlet 模式、窗口状态、当前呈现参数或 Portlet 首选项等。而通过 Portlet 呈现资源的优势是可以通过门户访问资源,因此可以通过控制门户访问而对资源提供保护。但是,这也带来了额外的门户请求开销,加重了门户服务器的负载。
为了更好的解决这两种方法的局限性,JSR 286 采用了一种新的资源服务方式 —— Portlet 资源服务。即 JSR 286 引入了一个新的具有 serveResource 方法的可选生命周期接口 ResourceServingPortlet ,该接口可以由 ResourceURL 触发,Portlet 可以通过 PortletResponse.createResourceURL 方法创建它。资源 URL 包含当前 Portlet 的瞬时状态(Portlet 模式、窗口状态和呈现参数),但不能为此状态设置新值。资源 URL 可以有在资源 URL 上设置的其他资源参数。
通过调用 ResourceServingPortlet 接口的 serveResource() 方法, Portlet 不仅可以通过控制门户访问而对资源进行保护,并且 Portlet 容器不会呈现任何除 serveResource() 方法返回的内容之外的附加输出。这样,用户由于可以直接通过操作响应对象而被赋予了更多的控制权限,并且没有额外门户请求的开销,减轻了门户服务的负载。而 Portal 服务器此时只是充当了一个代理服务器的作用。
JSR 286 资源服务的使用方法:
Portlet 类需实现 javax.portlet.Portlet 和 javax.portlet.ResourceServingPortlet 接口并实现 serveResource() 方法, 如 清单 4 所示:
清单 4. Portlet 对资源文件的访问控制
public class TestPortlet implements Portlet, ResourceServingPortlet
{
......
public void serveResource(ResourceRequest resourceRequest,
ResourceResponse resourceResponse) throws PortletException,
IOException {
...
在此添加访问控制等业务逻辑代码
...
PortletRequestDispatcher portletRequestDispatcher = portletConfig
.getPortletContext().getRequestDispatcher(
"/WEB-INF/jsp/TestPortletResource.jsp");
portletRequestDispatcher.include(resourceRequest, resourceResponse);
}
......
}
使用 JSP 标签通过PortletResponse.createResourceURL 方法创建 RecourceURL:
清单 5. 创建资源访问 URL
<a href="<portlet:resourceURL/>">Click me to request Resource URL</a>
所保护的访问资源,在此例中即为 TestPortletResource.jsp 。
接下来,我们就可以充分体验 JSR 286 资源服务新特性所带来的简单便捷以及高性能了。
对照该介绍,读者可参照本系列第 2 部分对资源服务特性的实例开发加深对该部分相关内容的理解。
事件
JSR 286 定义的事件模型是一种松耦合的代理事件模型。在此模型中,Portlet 定义可以接收以及在 Portlet 部署描述符中公布的事件。在运行时,门户管理员(或业务用户)可以将不同的 Portlet 连接在一起。
Portlet 事件服务并不是一个可信任消息服务(例如 JMS)的替代。很多情况下Portlet 事件并不能总是保证能够传送到目的地。因此 Portlet 必须能够在部分或即使所有事件都不能正确接收的情况下仍然能够工作。
另外,有的时候 Portlet 为了响应某一个事件,也会向另外的 Portlet 发布新的事件,这样就形成了事件的衍生代。这在一定程度上可能造成事件的死锁,JSR 286 本身没有对衍生代做出限制,但是很多 Portlet 容器会定义事件的最大衍生代以防止死锁的发生。读者在开发相关应用时请注意其本身的限制。
事件声明
对于一个事件的声明包括三个部分,分别是事件的定义声明、事件的发布载体声明也就是发布该事件的 Portlet 声明、事件接收载体的 Portlet 声明。
事件定义声明:我们需要在 portlet.xml 中使用 <event-definition> 元素对事件进行声明,并且该元素与 <portlet> 元素并列作为 <portlent-app> 的子元素。
清单 6.event-definition 声明
<portlet-app id="myPortletApp" version="2.0">
<portlet>
...
</portlet>
<event-definition> . . .</event-definition>
...
</portlet-app>
对于一个事件的声明有两点需要注意:事件的名称和值的类型。对于事件名称,JSR 286 既可以为事件定义默认的命名空间,其作用域为所有未声明 QName 的事件;也可以为事件单独定义自己的 QName。对于 QName 和命名空间的理解,请读者参考 XML 规范的相关文档,本文不做详细介绍。对于事件值的类型,既可以是简单的Java 对象,例如 Integer,String 等,也可以是预先定义的 Java 复杂对象,但是前提是该对象必须实现 Serializable 接口。其中 <event-definition> 的具体格式如 清单 7 和 清单 8 所示:
清单 7. 默认命名空间下事件定义声明
<default-namespace>http://cn.ibm.com/</default-namespace>
......
<event-definition>
<name>event-with-simple-value</name>
<value-type>java.lang.String</value-type>
</event-definition>
<event-definition>
<name>event-with-complex-value</name>
<value-type>com.ibm.jsr286.TestEventBean</value-type>
</event-definition>
清单 8. 自定义 QName 下事件定义声明
<event-definition>
<qname xmlns:key="http://www.ibm.com">event-with-simple-value</qname>
<value-type>java.lang.Integer</value-type>
</event-definition>
<event-definition>
<qname xmlns:key="http://www.ibm.com">event-with-qname</qname>
<value-type>com.ibm.jsr286..TestEventBean</value-type>
</event-definition>
事件发布载体声明:事件的发布载体声明需要在 portlet.xml 的 <portlet> 元素中用 <supported-publishing-event> 关键字。对应事件声明格式,事件发布载体 Portlet 声明亦有默认命名空间和自定义命名空间以及简单对象和复杂对象的情况,见示例:
清单 9. 默认命名空间下事件发布声明
<supported-publishing-event>
<name>event-with-simple-value</name>
</supported-publishing-event>
<supported-publishing-event>
<name>event-with-complex-value</name>
</supported-publishing-event>
清单 10. 自定义 QName 下事件发布声明
<supported-publishing-event>
<qname xmlns:key="http://www.ibm.com">event-with-simple-value</qname>
</supported-publishing-event>
<supported-publishing-event>
<qname xmlns:key="http://www.ibm.com">event-with-complex-value</qname>
</supported-publishing-event>
事件接收载体声明与事件发布载体声明类似,事件的接收载体声明需要在 portlet.xml 的 <portlet> 元素中用 <supported-processing-event> 关键字。见示例清单 11 和 清单 12:
清单 11. 默认命名空间下事件接收载体声明
<supported-processing-event>
<name>event-with-simple-value</name>
</supported-processing-event>
<supported-processing-event>
<name>event-with-complex-value</name>
</supported-processing-event>
清单 12: 自定义 QName 下事件接收载体声明
<supported-processing-event>
<qname xmlns:key="http://www.ibm.com">event-with-simple-value</qname>
</supported-processing-event>
<supported-processing-event>
<qname xmlns:key="http://www.ibm.com">event-with-complex-value</qname>
</supported-processing-event>
事件发布接收与处理
事件发布:我们可以在希望发布事件的 Portlet 的 processAction() 方法里,通过调用 ActionResponse 的 setEvent() 进行事件发布,setEvent() 方法的输入参数为事件的名称和对应的值,这些参数必须与我们前面在 portlet.xml 中的事件声明一致。
清单 13. 事件发布
public class TestSenderPortlet implements Portlet
{
......
public void processAction(ActionRequest request,
ActionResponse response) throws PortletException,
IOException {
......
response.setEvent(eventName, eventObject);
......
}
......
}
事件接收:事件的接收 Portlet 必须实现 javax.Portlet.EventPortlet 接口,事件的接收处理则在该接口包含 processEvent() 方法中进行,JSR 286 定义该方法提供了两个输入参数: EventRequest 和 EventResponse,我们可以通过调用 EventRequest 实例的 getEvent() 方法来获得当前事件,该方法返回一个事件对象的实例,该实例封装了事件的唯一标识和对应的值。
清单 14: 事件接收
public class TestReceiverPortlet implements Portlet,EventPortlet
{
......
public void processEvent(EventRequest request,
EventResponse response) throws PortletException,
IOException {
......
Event event = request.getEvent();
......
}
......
}
事件处理:获得事件对象后,我们也可以通过 getQNames() 方法或者 getName() 获得事件的名称。两种获得事件方法的区别是 getQNames() 可以得到事件的全称标识,而 getName() 只是取得本地标识名。而取得事件的值则可以通过事件的 getValue() 方法获得。
清单 15: 事件处理
......
public void processEvent(EventRequest request,
EventResponse response) throws PortletException,
IOException {
......
Event event = request.getEvent();
String name = event.getName();// 或者 String qname = event.getQNames();
String value = event.getValue();
......
}
......
GenericPortlet API 的变化
在 JSR 168 Portlet 的开发中,开发者通常继承抽象类 javax.portlet.GenericPortlet 来实现自己的 Portlet 逻辑代码。 同 JSR 168 相比, JSR 286 的 GenericPortlet 增加了 javax.portlet.EventPortlet 和 javax.portlet.ResourceServingPortlet接口的实现,从而增加了事件处理和资源服务的功能。读者可以从 类图 1 和类图 2 看出 JSR 286 GenericPortlet API 的变化。
图 1. JSR 168 GenericPortlet 类图
图 2. JSR 286GenericPortlet 类图
共享呈现参数
我们先来看一看共享呈现参数的官方介绍:共享呈现用于在 Portlet 之间共享呈现参数,从而创建一个页面上下文。共享呈现参数在 portlet.xml 文件中声明。如果这些参数不是空值,Portlet 将自动接收它们。与非共享呈现参数相比,如果应该更改值,Portlet 仅需要设置共享呈现参数。
共享呈现参数,顾名思义,就是指 Portlet 之间共享参数,每一个 Portlet 对该参数的修改都能够直接被另外支持该参数 Portlet 所获得。共享呈现参数与 JSR 168 中已经有私有呈现参数的区别就在于,私有呈现参数只为 Portlet 内部使用,而共享呈现参数则为多个 Portlet之间通信协作而设置。共享呈现参数与事件相比的优势就在于避免了事件处理过程调用的繁琐。
我们举一个简单的事例来说明共享呈现参数的优点。假如我们开发了一个关于天气的 Portlet, 这个 Portlet 可以根据选择的城市来显示该城市的天气情况。我们为这个 Portlet 定制了邮政编码这个共有呈现参数来表示用户选择的城市。这样,如果我们再开发一个也有这个共享呈现参数的 Portlet,例如显示该城市地图或者旅游信息的 Portlet。在这种情况下,当我们修改天气 Portlet 所选择的城市的话,地图以及旅游信息的 Portlet 也会自动做出相应变化。从而实现了不同 Portlet 之间的协作。
共享呈现参数的声明
对共享呈现参数的使用声明包括两个部分,对共享呈现参数定义的声明和支持共享呈现参数的 Portlet 声明。
共享呈现参数定义声明:对于共享呈现参数定义的声明必须在 portlet.xml 部署文件中使用 <public-render-parameter> 关键字,该元素与 <portlet> 元素并列为 <portlet-app> 的分支。
清单 16. 共享呈现参数定义声明
<portlet-app ...>
<portlet>
<portlet-name>Portlet A</portlet-name>
...
</portlet>
...
<public-render-parameter>
<identifier>public-render-param1</identifier>
</public-render-parameter>
<public-render-parameter>
<identifier>public-render-param2</identifier>
</public-render-parameter>
</portlet-app>
支持共享呈现参数 Portlet 声明:对于支持共享呈现参数的 Portlet 的声明需要在 portlet.xml 中 <portlet> 元素中使用 <supported-public-render-parameter> 关键字。
清单 17. 共享呈现参数 Portlet 定义声明
<portlet>
<portlet-name>Portlet B</portle-name>
......
<supported-public-render-parameter>
<identifier>public-render-param1</identifier>
</supported-public-render-parameter>
</portlet>
<portlet>
<portlet-name>Portlet C</portle-name>
......
<supported-public-render-parameter>
<identifier>public-render-param2</identifier>
</supported-public-render-parameter>
</portlet>
共享呈现参数的使用
与非共享呈现参数的使用方法相同,共享呈现参数可以通过 ActionResponse 的 setRenderParameter("标识","值") 方法设定,并通过 RenderRequest 的 getParameter("标识") 来获得。见 清单 18 和清单 19
清单 18. Portlet A 设定共享呈现参数
...
public void processAction(ActionRequest actionRequest,
ActionResponse actionResponse) throws PortletException, IOException {
String publicRenderParamValue1 = actionRequest.getParameter("public-render-param1");
actionResponse.setRenderParameter("public-render-param1", publicRenderParamValue1);
}
...
清单 19. Portlet B 获取共享呈现参数
...
public void render(RenderRequest renderRequest,
renderResponse renderResponse) throws PortletException, IOException {
...
String publicRenderParamValue1 = renderRequest.getParameter("public-render-param1");
...
}
......
Portlet 过滤器
Portlet 过滤器是 JSR 286 提供的有一个非常重要的新特性。事实上,在 JSR 286 之前,就已经有很多厂商(包括 IBM)自定义扩展了 JSR168,提供了过滤器功能。由此可见,Portlet 过滤器的重要性。为了避免各种厂商不同 Portlet 过滤器的不兼容性,JCP(JavaCommunity Process)对 JSR 286 定义了标准的过滤器实现。
什么是 Portlet 过滤器?
与 Servlet 相似,Portlet 过滤器可以使用户可以改变一个 request 和修改一个 response。Filter 不是一个 Portlet,它不能产生一个 response,它能够在一个 request 到达 Portlet 之前预处理 request,也可以在离开 Portlet 时处理 response。换句话说,过滤器其实是一个“Portlet chaining”(Portlet 链)。它能够实现的功能包括:
在 Portlet 被调用之前截获;
在 Portlet 被调用之前检查 servlet request;
根据需要修改 request 头和 request 数据;
根据需要修改 response 头和 response 数据;
在 Portlet 被调用之后截获;
Portlet 过滤器与 Servlet 过滤器
事实上,从宏观功能的角度看来,Portlet 过滤器和 Servlet 过滤器是很相似的。这是因为二者都可以声明性地嵌入,从而截获并修改请求和响应。但是理解它们之间存在着很大的不同是非常重要的。在一定程度上,它们之间的差异是与 Servlet 和Portlet 之间的差异相联系的:Servlet 过滤器是一个门户级过滤器,它可以修改由一些小的部分(来自页面上所有 Portlet 的响应)集合而成的整个门户页面;而 Portlet 过滤器只能用于那些小的部分。Servlet 过滤器(如果已经安装的话)是接收和修改客户端请求的第一个组件,同时也是修改对客户端的响应的最后一个组件(请参见图 3)。
图 3. 带有 Servlet 过滤器和 Portlet 过滤器的客户端请求事件序列
如图 3 所示,Servlet 请求(步骤 1)在分派给一个或多个 Portlet 请求(步骤 3)之前首先通过一个 Servlet 过滤器链进行处理(步骤 2)。在结果集聚到一起(步骤 6 和 7)之前,Portlet 请求进一步转发到 Portlet 过滤器链进行处理(步骤 4)。接着,将集聚的结果发送回Servlet 过滤器进行处理,之后,将集聚的结果最终显示给用户(步骤 9)。
另外一点需要注意的是,Servlet 过滤器比 Portlet 过滤器的优先级别要高,容器将首先进行 Servlet 过滤,其次是 Portlet 过滤。一个过滤器链包含一个或多个过滤器。在一个过滤器完成处理之后,新的请求和响应将传送到链上的下一个过滤器;链上的最后一个过滤器调用目标资源(Servlet 或 Portlet)。
Portlet 过滤器的工作原理
Portlet 过滤器可以放置在 V2.0 规范提供的任何生命周期方法调用的前面或者后面(processAction、processEvent、render、serveResource),而且还支持这些生命周期方法使用包装的请求和响应。与 Servlet 过滤器类似,Portlet 过滤器需要在 portlet.xml 部署描述符中进行定义,不同的是,servlet只有一个service()的请求处理方法,因此servlet只有一种类型的过滤器。而portlet 却有四种请求处理方法,于是有四种类型的过滤器,包括:
Action 过滤器
Render 过滤器
Resource 过滤器
Event 过滤器
下面我们来介绍四种过滤器的工作原理。
JSR 286 为 Portlet 过滤器提供了接口类 PortletFilter,该接口提供了两个方法。
void init(FilterConfig config)
容器调用一次这个方法来准备用于服务的过滤器。对象 filterConfig 使得过滤器能够访问配置参数以及对门户上下文的引用。
void destroy()
这个方法是在将过滤器从服务移除之后调用的。这个方法使得过滤器能够清除任何存放的资源。
JSR 286 为四种过滤器分别定义了一个接口类,这四个接口类都继承 PortletFilter 类,并分别添加了各自 doFilter() 方法。关于这几个类之间的关系,请见下图:
图 4. Portlet 过滤器继承图
四种过滤器分别对 Portlet 的四个方法进行拦截。用户自定义的过滤器必须实现相应的过滤器接口,通过其 doFilter() 方法来实现相应的动作。其对应关系见表 1:
表 1. 过滤器与拦截方法关系图
过滤器 | 拦截方法 |
Action 过滤器 | processAction(ActionRequest request, ActionResponse response) |
Render 过滤器 | render(RenderRequest request, RenderResponse response) |
Resource 过滤器 | serveResource(ResourceRequest request, ResourceResponse response) |
Event 过滤器 | processEvent(EventRequest request, EventResponse response) |
Portlet 过滤器的部署声明
下面我们通过一个具体的 Portlet 过滤器部署实例来说明。参见清单 20:
清单 20. Portlet过滤器声明
<portlet-app ...>
...
<filter>
<filter-name>TestRenderFilter</filter-name>
<filter-class>com.ibm.jsr286.TestRenderFilter</filter-class>
<lifecycle>RENDER_PHASE</lifecycle>
</filter>
<filter>
<filter-name>TestAllFilter</filter-name>
<filter-class>com.ibm.jsr286.TestAllFilter</filter-class>
<lifecycle>ACTION_PHASE</lifecycle>
<lifecycle>RENDER_PHASE</lifecycle>
<lifecycle>EVENT_PHASE</lifecycle>
<lifecycle>RESOURCE_PHASE</lifecycle>
</filter>
<filter-mapping>
<filter-name>TestRenderFilter</filter-name>
<portlet-name>DocumentPortlet</portlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>TestAllFilter</filter-name>
<portlet-name>Test*</portlet-name>
</filter-mapping>
</portlet-app>
关于 Portlet 过滤器有几点需要声明的是:
对于一个 Portlet 过滤器的声明亦包括两部分,过滤器的定义声明以及过滤器的映射声明。
一个 Portlet 过滤器可以为多个 Portlet 服务,而且 一个 Portlet 可以同时有多个 Portlet 过滤器。
一个 Portlet 过滤器可以有多个生命周期阶段,当然前提是该 Portlet 过滤器实现了相应过滤器接口。
Portlet 窗口
Portlet 窗口在 JSR 168 中仅间接地得到反映,并作为容器为 Portlet 范围的会话条目生成的前缀的一部分。JSR 286 规范现在使该 Portlet 窗口 ID 可通过请求供 Portlet使用,简单的说就是,PortletRequest新增了一个方法getWindowID(),可以获得 Portlet 的窗口 ID,这个 ID 是由容器生成的。在Portal 容器中布局同一个 Portlet 多次的情况下,windowID 可以用来区分同一个 Portlet 的不同窗口,从而可以使这些 Portlet 窗口缓存并呈现不同的数据。
Portlet 窗口支持的一个常见用例是在 Portlet 中缓存数据;例如,一个 Portlet,呈现所使用的数据是通过 web services 从远端服务器获得,而且网络调用速度比较慢,且呈现的数据为只读,这种情况下就可以把 web services 获得的数据和窗口 ID 关联并缓存起来,在频繁调用Portlet 的时候,就不必频繁等待 web services 的返回。
· 第二部分 资源服务、事件与共享呈现参数
======================================================================
本文将首先介绍 JSR 286 参考实现 Apache Pluto 2.0 平台的构建过程,然后通过在 Apache Pluto 2.0 平台上开发和部署 JSR 286 Portlet 应用程序, 向读者介绍 JSR 286 Portlet 资源服务和新增的交互功能:事件和共享呈现参数。
在本系列的 第 1 部分简要回顾了 JSR 168 Portlet,并对 JSR 286 Portlet 的新增特性做了详细的介绍, 本文将通过在 Apache Pluto 2.0 平台上开发和部署 Portlet 应用程序, 向读者介绍 JSR 286 Portlet 新特性的使用方法。本文将首先介绍 JSR 286 参考实现 Apache Pluto 2.0 平台的构建过程,然后通过在 Apache Pluto 2.0 平台上开发和部署 JSR 286 Portlet 应用程序, 向读者介绍 JSR 286 Portlet 资源服务和新增的交互功能:事件和共享呈现参数。
关于本系列
本系列 专门针对具有 JSR 168 Portlet 开发基础,并且想了解 JSR 286 Portlet 新特性和开发流程的开发人员。在学完本系列后,您将了解到相对于 JSR168 Portlet,JSR 286Portlet 究竟提供了哪些增强功能, 以及这些新增特性在实际开发中的应用。
本系列的 第 1 部分简单回顾了 JSR 168 Portlet, 并列出了 JSR 286 Portlet 的新增内容。第 2 部分和第 3 部分将通过在 Apache Pluto 2.0 平台上开发和部署 Portlet 应用程序, 向读者介绍 JSR 286 Portlet 新特性的使用方法。
关于本文
本文将首先介绍 JSR 286 参考实现 Apache Pluto 2.0 平台的构建过程,然后通过在 Apache Pluto 2.0 平台上开发和部署 JSR 286 Portlet 应用程序, 向读者介绍 JSR 286 Portlet 资源服务和新增的交互功能:事件和共享呈现参数。
Portlet 过滤器和 Portlet 窗口方面应用程序的开发过程,将在第 3 部分进行详细介绍。
在示例应用程序的开发和部署中用到了下列产品:
Sun JDK 1.5
Apache Tomcat 6.x
Apache Pluto 2.0
Apache Maven 2.x
Eclipse Europa(Eclipse V3.3) for JavaEE Developers
阅读本文之前,您应当对 JSR 168 Portlet 有所了解,并阅读了本系列的第 1 部分。
准备工作
Apache Pluto 2.0 是 JSR 286 的参考实现,是实现了 Portlet 2.0 API 的 Portlet 容器,充当 Portlet 的运行时环境,与 web 应用服务器的 Servlet容器的运行时环境支持Servlet 的情形非常相似。Pluto2.0 目前支持的 JSR 286Portlet 新特性有资源服务、事件、Portlet过滤器、共享呈现参数、Portlet 窗口。
在本文中,我们将使用 Apache Pluto 2.0 开发测试我们的 JSR 286 Portlet 应用程序。以下操作均在 Windows XP操作系统环境下进行。
1. 构建 JSR 286Portlet 运行环境 ApachePluto 2.0
Apache Pluto 2.0 目前还处于开发阶段,我们只能通过其源代码构建出一个支持 JSR 286 Portlet 标准的 Portlet 2.0 容器。
安装 Sun JDK 1.5 并设定环境变量
该步骤一般读者都比较熟悉,不再拗述。需要注意的是,经过笔者测试,Pluto 2.0 源码工程只可以在 Sun JDK 1.5 下构建成功,笔者使用 Sun JDK 1.6 和 IBM JDK 1.5 均构建失败。
安装 Maven 2
Pluto 源代码使用 Maven 2 进行项目管理和构建,我们必须首先安装该工具。
从 http://maven.apache.org/ 上寻找 Maven 2 的最新版本压缩包,下载并解压,设定 Maven 2 的安装路径为 ${M2_HOME}。将 ${M2_HOME}\bin 目录加到系统的 PATH 环境变量中。
安装 Tomcat 6
从 http://tomcat.apache.org/ 上寻找 Tomcat 6 的最新版本压缩包,下载并解压,设定安装路径为 ${TOMCAT_HOME}。
获取 Apache Pluto 2.0 源码
使用 SVN 客户端从官方 SVN 服务器上获得源代码:
清单 1. 使用 SVN 客户端从官方 SVN 服务器上获得源代码
svn checkout https://svn.apache.org/repos/asf/portals/pluto/trunk/ pluto2
使用 Maven 2 构建 Pluto 2.0
编辑 ${M2_HOME}\conf 目录下的 settings.xml 文件,增加 <pluginGroups> 元素:
清单 2. settings.xml 文件
<settings>
...
<pluginGroups>
<pluginGroup>org.apache.pluto</pluginGroup>
</pluginGroups>
...
</settings>
打开 pluto2 目录下的 pom.xml 文件,找到
清单 3. pom.xml 文件
...
<jaxb-impl.version>2.1.2</jaxb-impl.version>
...
改为
清单 4. pom.xml 文件
<jaxb-impl.version>2.1.3</jaxb-impl.version>
命令行模式下进入 pluto2 目录,执行以下命令:
清单 5.
D:\>cd pluto2
D:\pluto2>mvn install
D:\pluto2>mvn pluto:install -DinstallDir=${TOMCAT_HOME}
如果您的 Tomcat 安装路径中存在空格,则需要用双引号把路径引起来:
清单 6.
mvn pluto:install
-DinstallDir="C:\Program Files\Apache Software Foundation\Tomcat 6.0"
从网上寻找 commons-logging-api-1.1.jar 文件,拷贝到 ${TOMCAT_HOME}\lib\ 目录下。
至此,pluto2.0 的相关文件就被安装到 tomcat 相应目录下。
编辑 ${TOMCAT_HOME}\conf\tomcat-users.xml 文件,添加角色 pluto,并在该角色下新增一个用户,以下为示例文件:
清单 7. tomcat-users.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users>
<role rolename="role1"/>
<role rolename="pluto"/>
<role rolename="tomcat"/>
<role rolename="manager"/>
<user password="pluto" roles="pluto,manager" username="pluto"/>
<user password="tomcat" roles="role1" username="role1"/>
<user password="tomcat" roles="tomcat,role1" username="both"/>
<user password="tomcat" roles="tomcat,pluto,manager" username="tomcat"/>
</tomcat-users>
验证安装
运行 ${TOMCAT_HOME}\bin\startup.bat,启动 tomcat 服务器。浏览器访问 URL http://localhost:8080/pluto/portal,如图 1 所示:
图 1. Pluto 登录界面
查看原图(大图)
输入添加到 pluto 角色的用户名和密码,进入 Pluto 的 Portal 页面:
图 2. Pluto Portal界面
查看原图(大图)
至此,JSR 286 Portlet 运行环境 Apache Pluto 2.0 搭建成功。
2. 使用 EclipseEuropa 建立开发环境
首先,需要从 Eclipse 官方网站 http://www.eclipse.org 下载 Eclipse Europa,针对不同的开发需求,有几种包可供下载。我们进行的是 J2EE Web 开发,所以注意要下载 Eclipse IDE for Java EE Developers。
启动 Eclipse,对 Eclipse 进行配置:
执行菜单项目 Window -> Preferences,打开 Preferences 对话框,选择 Server -> Installed Runtimes 项,如图 3 所示:
图 3. Preferences 对话框
查看原图(大图)
点击 Add 按钮,将 Tomcat 6 添加为运行时,如 图 4、图 5 所示:
图 4. 选择运行时类型
图 5. 设定 Tomcat 安装路径
单击 Finish 结束配置,单击 OK 关闭 Preferences对话框。
在 Eclipse 的 Servers 视图中单击鼠标右键,选择 New -> Server。如 图 6 所示:
图 6. 新建服务器
在弹出的窗口中选择目标运行服务器 Apache Tomcat 6.0 Server,运行时呈现 Apache Tomcat v6.0,如图7所示,点击 Finish。
图 7. 选择目标运行服务器
查看原图(大图)
在 Servers 视图中双击刚刚新建的 Tomcat 服务器,打开服务器配置页面,如图 8 所示:
图 8. Tomcat 服务器配置页面
查看原图(大图)
在 Server Locations 中选择 Use Tomcat Installation,Deploy Path选择 ${TOMCAT_HOME}\webapps,如图 9 所示。至此开发环境设置完毕。
必须设定 Deploy Path 为 Tomcat 安装目录下的 webapps 目录,否则使用 Eclipse 启动 Tomcat 后,Pluto 不能加载进来。
图 9. Tomcat 服务器配置
创建 JSR 286 Portlet 应用
下面,我们开始创建一系列程序来演示 JSR 286 Portlet 的新特性。主要分为以下六个部分:
使用 Eclipse 创建 Portlet Web 项目
资源服务
事件
Portlet 过滤器
共享呈现参数
Portlet 窗口
1. 使用 Eclipse 创建 Portlet Web 项目
新建项目,项目类型选择 Web->Dynamic Web Project,如图 10 所示:
图 10. 新建动态 Web 项目
接下来,设置项目属性,项目名称 jsr286portlets, 目标运行时 Apache Tomcat V6.0,保留默认设置,点击 Finish,如 图 11 所示:
图 11. 设置项目属性
查看原图(大图)
生成项目结构如图 12:
图 12. 项目结构
在 META-INF 下新建 context.xml 文件,内容如下:
清单 8. context.xml 文件
<Context crossContext="true" />
该文件为 Tomcat 的特有配置文件,根据 Pluto 的要求,该 Web 工程的上下文应该可以被其它JavaEE 程序访问,所以crossContext 参数设置为 true。
在 WEB-INF 下新建 portlet.xml 文件,内容如下:
清单 9. portlet.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<portlet-app
xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd
http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
version="2.0">
<!-- 在该位置填写portlet描述内容 -->
</portlet-app>
下面我们将在黑体部分填写 portlet 定义。
2. 资源服务
新增 Java 类 TestPortlet,实现了 javax.portlet.Portlet 和javax.portlet.ResourceServingPortlet 接口:
清单 10. TestPortlet.java 文件
package com.ibm.samples.jsr286.portlets;
import ...
public class TestPortlet implements Portlet, ResourceServingPortlet {
private PortletConfig portletConfig;
public void init(PortletConfig portletConfig) throws PortletException {
this.portletConfig = portletConfig;
}
public void destroy() {
}
public void processAction(ActionRequest actionRequest,
ActionResponse actionResponse) throws PortletException, IOException {
}
public void render(RenderRequest renderRequest,
RenderResponse renderResponse) throws PortletException, IOException {
PortletRequestDispatcher portletRequestDispatcher = portletConfig
.getPortletContext().getRequestDispatcher(
"/WEB-INF/jsp/TestPortletView.jsp");
portletRequestDispatcher.include(renderRequest, renderResponse);
}
public void serveResource(ResourceRequest resourceRequest,
ResourceResponse resourceResponse) throws PortletException,
IOException {
PortletRequestDispatcher portletRequestDispatcher = portletConfig
.getPortletContext().getRequestDispatcher(
"/WEB-INF/jsp/TestPortletResource.jsp");
portletRequestDispatcher.include(resourceRequest, resourceResponse);
}
}
在 WEB-INF 目录下新建 jsp 目录,在 jsp 目录下新建 portlet 呈现阶段所显示的 jsp 文件 TestPortletView.jsp。
清单 11. TestPortletView.jsp 文件
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0"%>
<portlet:defineObjects />
<table>
<tr>
<td><h1>Test portlet page.</h1></td>
</tr>
<tr>
<td><a href="<portlet:resourceURL/>">Click me to request Resource URL</a></td>
</tr>
</table>
在 jsp 目录下新建 portlet 资源服务所请求的 jsp 文件 TestPortletResource.jsp。
清单 12. TestPortletResource.jsp 文件
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0"%>
<portlet:defineObjects />
<table>
<tr>
<td><h1>Test portlet resource page.</h1></td>
</tr>
</table>
编辑 portlet.xml 文件, 为 TestPortlet 增加一个 portlet 定义片断,该 TestPortlet 仅支持 View 模式。
清单 13. TestPortlet 定义片断
<portlet>
<portlet-name>TestPortlet</portlet-name>
<display-name>TestPortlet</display-name>
<portlet-class>com.ibm.samples.jsr286.portlets.TestPortlet</portlet-class>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>VIEW</portlet-mode>
</supports>
<portlet-info>
<title>TestPortlet</title>
</portlet-info>
</portlet>
编辑 web.xml 文件,增加 Pluto 所需的 servlet 定义及映射。读者请注意,该定义为 Pluto 2.0 Portlet 容器所需,不属于 JSR 286 标准的要求。
清单 14. Pluto 所需的 servlet 定义及映射片断
<servlet>
<servlet-name>TestPortlet</servlet-name>
<servlet-class>
org.apache.pluto.core.PortletServlet
</servlet-class>
<init-param>
<param-name>portlet-name</param-name>
<param-value>TestPortlet</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>TestPortlet</servlet-name>
<url-pattern>/PlutoInvoker/TestPortlet</url-pattern>
</servlet-mapping>
· 第三部分 Portlet 过滤器和Portlet 窗口
======================================================================
在 本系列 的 第 1 部分简要回顾了JSR 168 Portlet,并对 JSR 286 Portlet 的新增特性做了详细的介绍,第 2 部分 和第 3 部分将通过在 ApachePluto 2.0 平台上开发和部署Portlet 应用程序, 向读者介绍 JSR 286 Portlet 新特性的使用方法。本文将介绍 JSR 286 Portlet 的 Portlet 过滤器和 Portlet 窗口应用程序开发。
关于本系列
本系列 专门针对具有 JSR 168 Portlet 开发基础,并且想了解 JSR 286 Portlet 新特性和开发流程的开发人员。在学完本系列后,您将了解到相对于 JSR168 Portlet,JSR 286Portlet 究竟提供了哪些增强功能, 以及这些新增特性在实际开发中的应用。
本系列的 第 1 部分简单回顾了 JSR 168 Portlet, 并列出了 JSR 286 Portlet 的新增内容。第 2 部分 和第 3 部分将通过在 ApachePluto 2.0 平台上开发和部署Portlet 应用程序,向读者介绍 JSR286 Portlet 新特性的使用方法。
关于本文
本文承接 第 2 部分,继续介绍 JSR 286 Portlet 的 Portlet 过滤器和 Portlet 窗口应用程序开发。阅读本文之前,您应当对 JSR 168 Portlet 有所了解,并阅读了本系列的第 1 部分和 第 2 部分。
Portlet 过滤器
通过 第 1 部分的介绍,我们知道 Portlet 过滤器分为:
Action 过滤器
Render 过滤器
Resource 过滤器
Event 过滤器
我们将首先对这四种 Portlet 过滤器的开发使用流程分别单独进行介绍,然后将这四种 Portlet 过滤器综合起来进行更进一步的开发,最后通过和 Servlet 过滤器的结合使用,使读者明白 Portlet 过滤器和 Servlet 过滤器的关系和区别。
Action 过滤器
新建 Java 类TestActionFilter:
清单 1. TestActionFilter.java 文件
package com.ibm.samples.jsr286.filters;
import ...
public class TestActionFilter implements ActionFilter {
private static Log log = LogFactory.getLog(TestActionFilter.class);
private FilterConfig filterConfig;
public void init(FilterConfig filterConfig) throws PortletException {
log.info("action filter [" + filterConfig.getFilterName()
+ "] is initialized.");
this.filterConfig = filterConfig;
}
public void destroy() {
log.info("action filter [" + filterConfig.getFilterName()
+ "] is destroyed.");
}
public void doFilter(ActionRequest actionRequest,
ActionResponse actionResponse, FilterChain filterChain)
throws IOException, PortletException {
log.info("action filter [" + filterConfig.getFilterName()
+ "] is called.");
filterChain.doFilter(actionRequest, actionResponse);
}
}
这个程序的主要作用就是在 Action Filter 初始化、过滤器调用,销毁的时候分别打印相应的信息。清单 1 中filterChain.doFilter(actionRequest, actionResponse) 需要读者特别注意,这行代码保证了过滤器链的传递,删去这行代码,则过滤器链将在该过滤器执行结束后终结。
编辑 portlet.xml 文件,加入如下片断:
清单 2. Action 过滤器定义
<portlet-app ...>
...
<filter>
<display-name>TestActionFilter</display-name>
<filter-name>TestActionFilter</filter-name>
<filter-class>com.ibm.samples.jsr286.filters.TestActionFilter</filter-class>
<lifecycle>ACTION_PHASE</lifecycle>
</filter>
...
</portlet-app>
定义 Action 过滤器映射,可以影射到具体某个 Portlet, 或者根据模式匹配到一组 Portlet:
清单 3. Action 过滤器映射
...
<filter-mapping>
<filter-name>TestActionFilter</filter-name>
<portlet-name>*</portlet-name>
</filter-mapping>
...
在清单 3 的定义中,我们声明TestActionFilter 对所有 Portlet 的 processAction 调用进行拦截。
重启 Web 应用程序并将TestPortlet 部署到 "TestJSR 286 Portlet Page" 页面, 并将该页面其它所有Portlet 移除,输入数据,点击Submit 按钮触发processAction 调用,EclipseConsole 出现如下输出:
清单 4. Action 过滤器调用结果
...
2008-3-16 22:35:20 com.ibm.samples.jsr286.filters.TestActionFilter init
信息: action filter [TestActionFilter] is initialized.
2008-3-16 22:35:24 com.ibm.samples.jsr286.filters.TestActionFilter doFilter
信息: action filter [TestActionFilter] is called.
2008-3-16 22:35:24 com.ibm.samples.jsr286.filters.TestActionFilter destroy
信息: action filter [TestActionFilter] is destroyed.
...
从上面的信息可以看出,对于 Portlet 的每次 processAction 调用,Action Filter 都要经历一个初始化、过滤方法 doFilter 调用、销毁的全过程。读者可以多次实验证实这一点。
Render 过滤器
新建 Java 类TestRenderFilter:
清单 5. TestRenderFilter.java 文件
package com.ibm.samples.jsr286.filters;
import ...
public class TestRenderFilter implements RenderFilter {
private static Log log = LogFactory.getLog(TestRenderFilter.class);
private FilterConfig filterConfig;
public void init(FilterConfig filterConfig) throws PortletException {
log.info("render filter [" + filterConfig.getFilterName()
+ "] is initialized.");
this.filterConfig = filterConfig;
}
public void destroy() {
log.info("render filter [" + filterConfig.getFilterName()
+ "] is destroyed.");
}
public void doFilter(RenderRequest renderRequest,
RenderResponse renderResponse, FilterChain filterChain)
throws IOException, PortletException {
log.info("render filter [" + filterConfig.getFilterName()
+ "] is called.");
filterChain.doFilter(renderRequest, renderResponse);
}
}
这个程序的主要作用就是在 Render Filter 初始化、过滤器调用,销毁的时候分别打印相应的信息。和 Action Filter 一样,读者同样需要注意过滤链传递的问题。
编辑 portlet.xml 文件,加入如下片断:
清单 6. Render 过滤器定义
<portlet-app ...>
...
<filter>
<display-name>TestRenderFilter</display-name>
<filter-name>TestRenderFilter</filter-name>
<filter-class>com.ibm.samples.jsr286.filters.TestRenderFilter</filter-class>
<lifecycle>RENDER_PHASE</lifecycle>
</filter>
...
</portlet-app>
定义 Render 过滤器映射,可以影射到具体某个 Portlet, 或者根据模式匹配到一组 Portlet:
清单 7. Render 过滤器映射
...
<filter-mapping>
<filter-name>TestRenderFilter</filter-name>
<portlet-name>*</portlet-name>
</filter-mapping>
...
在清单 7 的定义中,我们声明TestRenderFilter 对所有 Portlet 的 render 调用进行拦截。
重启 Web 应用程序并将多个 Portlet部署到 "TestJSR 286 Portlet Page"页面, 访问该页面,EclipseConsole 出现多个如下输出:
清单 8. Render 过滤器调用结果
...
2008-3-16 22:53:11 com.ibm.samples.jsr286.filters.TestRenderFilter init
信息: render filter [TestRenderFilter] is initialized.
2008-3-16 22:53:11 com.ibm.samples.jsr286.filters.TestRenderFilter doFilter
信息: render filter [TestRenderFilter] is called.
2008-3-16 22:53:11 com.ibm.samples.jsr286.filters.TestRenderFilter destroy
信息: render filter [TestRenderFilter] is destroyed.
...
从上面的信息可以看出,对于 Portlet 的每次 render 调用,Render Filter 都要经历一个初始化、过滤方法 doFilter 调用、销毁的全过程。以上信息出现的次数与部署到页面上的 Portlet 个数相同,意味着 Portlet 过滤器的拦截是分别针对每个 Portlet 进行的。
Resource 过滤器
新建 Java 类TestResourceFilter
清单 9. TestResourceFilter.java 文件
package com.ibm.samples.jsr286.filters;
import ...
public class TestResourceFilter implements ResourceFilter {
private static Log log = LogFactory.getLog(TestResourceFilter.class);
private FilterConfig filterConfig;
public void init(FilterConfig filterConfig) throws PortletException {
log.info("resource filter [" + filterConfig.getFilterName()
+ "] is initialized.");
this.filterConfig = filterConfig;
}
public void destroy() {
log.info("resource filter [" + filterConfig.getFilterName()
+ "] is destroyed.");
}
public void doFilter(ResourceRequest resourceRequest,
ResourceResponse resourceResponse, FilterChain filterChain)
throws IOException, PortletException {
log.info("resource filter [" + filterConfig.getFilterName()
+ "] is called.");
filterChain.doFilter(resourceRequest, resourceResponse);
}
}
这个程序的主要作用就是在 Resource Filter 初始化、过滤器调用,销毁的时候分别打印相应的信息,读者同样需要注意过滤链传递的问题。
编辑 portlet.xml 文件,加入如下片断:
清单 10. Resource 过滤器定义
<portlet-app ...>
...
<filter>
<display-name>TestResourceFilter</display-name>
<filter-name>TestResourceFilter</filter-name>
<filter-class>com.ibm.samples.jsr286.filters.TestResourceFilter</filter-class>
<lifecycle>RESOURCE_PHASE</lifecycle>
</filter>
...
</portlet-app>
定义 Resource 过滤器映射,可以影射到具体某个 Portlet, 或者根据模式匹配到一组 Portlet:
清单 11. Resource 过滤器映射
...
<filter-mapping>
<filter-name>TestResourceFilter</filter-name>
<portlet-name>*</portlet-name>
</filter-mapping>
...
在清单 11 的定义中,我们声明TestResourceFilter 对所有 Portlet 的 serveResource 调用进行拦截。
重启 Web 应用程序并将TestPortlet 部署到 "TestJSR 286 Portlet Page"页面, 访问该页面, 点击超链接“Click me to request ResourceURL”请求资源,EclipseConsole 出现如下输出:
清单 12. Resource 过滤器调用结果
...
2008-3-17 13:21:03 com.ibm.samples.jsr286.filters.TestResourceFilter init
信息: resource filter [TestResourceFilter] is initialized.
2008-3-17 13:21:03 com.ibm.samples.jsr286.filters.TestResourceFilter doFilter
信息: resource filter [TestResourceFilter] is called.
2008-3-17 13:21:05 com.ibm.samples.jsr286.filters.TestResourceFilter destroy
信息: resource filter [TestResourceFilter] is destroyed.
...
从上面的信息可以看出,对于 Portlet 的每次 serveResource 调用,Resource Filter 都要经历一个初始化、过滤方法 doFilter 调用、销毁的全过程。
Event 过滤器
新建 Java 类TestEventFilter
清单 13. TestEventFilter.java 文件
package com.ibm.samples.jsr286.filters;
import ...
public class TestEventFilter implements EventFilter {
private static Log log = LogFactory.getLog(TestEventFilter.class);
private FilterConfig filterConfig;
public void init(FilterConfig filterConfig) throws PortletException {
log.info("event filter [" + filterConfig.getFilterName()
+ "] is initialized.");
this.filterConfig = filterConfig;
}
public void destroy() {
log.info("event filter [" + filterConfig.getFilterName()
+ "] is destroyed.");
}
public void doFilter(EventRequest eventRequest,
EventResponse eventResponse, FilterChain filterChain)
throws IOException, PortletException {
log.info("event filter [" + filterConfig.getFilterName()
+ "] is called.");
Event event = eventRequest.getEvent();
log.info("event name: " + event.getName());
log.info("event qname: " + event.getQName());
log.info("event value: " + event.getValue().toString());
filterChain.doFilter(eventRequest, eventResponse);
}
}
这个程序的主要作用就是在 Event Filter 初始化、销毁的时候分别打印相应的信息,在 doFilter 方法中,截获事件的名称、QName 和 事件值,读者同样需要注意过滤链传递的问题。
编辑 portlet.xml 文件,加入如下片断:
清单 14. Event 过滤器定义
<portlet-app ...>
...
<filter>
<display-name>TestEventFilter</display-name>
<filter-name>TestEventFilter</filter-name>
<filter-class>com.ibm.samples.jsr286.filters.TestEventFilter</filter-class>
<lifecycle>EVENT_PHASE</lifecycle>
</filter>
...
</portlet-app>
定义 Event 过滤器映射,可以影射到具体某个 Portlet, 或者根据模式匹配到一组 Portlet:
清单 15. Event 过滤器映射
...
<filter-mapping>
<filter-name>TestEventFilter</filter-name>
<portlet-name>*</portlet-name>
</filter-mapping>
...
在清单 15 的定义中,我们声明TestEventFilter 对所有 Portlet 的 processEvent 调用进行拦截。
重启 Web 应用程序并将TestSimpleEventSenderPortlet、TestSimpleEventReceiverPortlet、 TestComplexEventSenderPortlet、TestComplexEventReceiverPortlet 部署到 "Test JSR 286 Portlet Page" 页面, 访问该页面,点击 TestSimpleEventSenderPortlet 按钮,Eclipse Console 出现如下输出:
清单 16. Event 过滤器调用结果
...
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter init
信息: event filter [TestEventFilter] is initialized.
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter doFilter
信息: event filter [TestEventFilter] is called.
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter doFilter
信息: event name: simple-event
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter doFilter
信息: event qname: {http://cn.ibm.com/}simple-event
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter doFilter
信息: event value: simple-event is sent by TestSimpleEventSenderPortlet
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter destroy
信息: event filter [TestEventFilter] is destroyed.
...
从上面的信息可以看出,对于 Portlet 的每次发送事件行为,Event Filter 都要经历一个初始化、过滤方法 doFilter 调用、销毁的全过程。从清单 16 也可以看到过滤器捕获到的事件信息。
发送复杂事件的过滤器结果捕获读者可以自行测试。
综合使用 Portlet 过滤器
Portlet 的四种过滤器可以集成到一个类中去实现,只要该类实现了上述四个接口即可。以下为类 TestAllPhaseFilter 的类图:
图 1. TestAllPhaseFilter 的继承关系