IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  WebSphere  >

使用 WebSphere Portal V5.1 在 IBM Portlet 和 JSR 168 Portlet 间共享信息

创建用于共享 Portlet 服务的自定义属性

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

讨论

样例代码


级别: 中级

Stefan Hepper (sthepper@de.ibm.com), WebSphere Portal 编程模型架构师, IBM
Jerry Zheng (jerryz@ca.ibm.com), 解决方案架构师兼开发负责人,WebSphere Commerce Enabled Portal, IBM

2006 年 4 月 25 日

交换信息的 Portlet 的功能(也称为 Portlet 间的通信协作 Portlet)是由 WebSphere Portal 中的属性代理提供支持的。WebSphere Portal V5.1.0.1 属性代理并不支持 IBM Portlet 和 JSR 168 Portlet 间的通信,因为它们在彼此独立的 Portlet 容器中运行。本文将说明如何编写自定义 Portlet 服务来允许遗留 IBM Portlet 和 JSR 168 Portlet 通过采用属性的方式共享信息。您还将了解如何使用动态 WebSphere Application Server 缓存功能在分布式环境中管理共享信息属性的生命周期。为了能尽可能地从本文受益,您应该对 Java Portlet 编程具有很好的了解,并且对 WebSphere Portal Portlet 服务功能具有基本的认识。有关可能帮助您获得这些基础知识的链接,请参阅参考资料

引言

Portlet 是基于 Java 的 Web 组件,用于处理请求和生成动态内容。这个由 Portlet 生成的内容称为片段,由遵守特定规则的标记(如 HTML、XHTML、WML)进行定义。可对片段进行聚合,以形成完整的文档,称为门户页。

Portlet 经常需要彼此进行通信,以共享信息。WebSphere Portal 属性代理为 Portlet 提供了在彼此间发送和接收属性的功能。尽管这些协作 Portlet 可以独立开发和部署,但仍然能以无缝的方式彼此进行通信,从而为最终用户产生一个结果页。

WebSphere Portal 属性代理可帮助采用相同 API 编写且驻留在相同或不同页面(已在 V5.1.0.1 中添加对后者的支持)的 Portlet 进行通信。协作 Portlet 通过采用 Web 服务描述语言(Web Services Definition Language,WSDL)格式声明属性(希望共享的信息)或使用编程 API(仅限于 IBM Portlet)来向属性代理进行订阅。开发人员可将 Portlet 定义为源 Portlet(发送属性)或目标 Portlet(接收属性)。管理员将源和目标 Portlet 连接到一起后,属性代理将对这些 Portlet 间的共享属性进行匹配,并将这些属性从源 Portlet 传输到目标 Portlet。有关协作 Portlet 和 WebSphere Portal 属性代理的更多信息,请参阅 WebSphere Portal 信息中心

WebSphere Portal V5.1 中的属性代理并不允许 IBM Portlet 和 JSR 168 Portlet 之间进行通信,因为它们在彼此独立的 Portlet 容器中运行。这就限制了属性代理在遗留 IBM Portlet 和标准 JSR 168 Portlet 中共存情况下的使用。由于 J2EE 禁止在 Portlet 应用程序间使用 HttpRequest 对象,此限制的一种常见解决方法是使用 PortletSession 对象来在 Portlet 间共享信息。不过,这也不能正常工作,因为根据 JSR 168 规范 1.0 的规定,PortletSession 的作用范围应设置为 Portlet 或 Portlet 应用程序上下文级别。IBM Portlet 和 JSR 168 Portlet 无法直接打包到相同的 Portlet 应用程序中。为了在 IBM Portlet 和 JSR 168 Portlet 之间共享信息,要求使用自定义解决方案。

本文将说明可以如何使用 WebSphere Portal Portlet 服务基础结构来实现自定义 Portlet 服务,以充当消极属性共享服务,从而在整个门户内不同 Portlet 应用程序中的任意 Portlet 间交换数据。通过使用此自定义 Portlet 服务,您可以实现以下目标:

  • 全局共享 Portlet 间的信息
  • 在用户会话内共享 Portlet 间的信息

本文还演示了如何使用 WebSphere Application Server 中的动态缓存功能来在分布式环境中管理服务内的存储对象的生命周期。

您可以选择对此自定义 Portlet 服务进行扩展,以用于信息共享之外的其他用途。例如,您可以使用此服务来缓存任何不适合保存在 PortletSession 对象内的 Java 对象。例如,假定有一个用于报告天气的 Portlet;您可能不希望每次用户刷新视图或提交新请求时都连接到后台天气服务器,因为此操作可能会带来很大开销。您可以转而创建一个邮政编码和天气信息的散列图,并将此信息保存到 Portlet 服务中。然后,您仅需要在一定间隔之后连接到后台服务器(如每三十分钟),以刷新数据。通过此解决方案,门户的所有用户都可以从缓存的散列图中共享相同的天气信息,而性能开销却要少得多。

本文的内容和示例适用于 IBM WebSphere Portal V5.1(或更高版本)和 IBM WebSphere Application server V5.1(或更高版本)。

关于示例

您可以下载自定义 Portlet 的示例代码和两个示例 Portlet(一个 IBM Portlet 和一个 JSR 168 Portlet),以在阅读本文过程中作为参考。

Portlet 服务功能回顾

Portlet 服务是向 Portlet 提供常见功能的服务。WebSphere Portal 支持两种类型的服务:缺省 Portlet 服务(随产品一起提供)和自定义 Portlet 服务(由您或别人创建)。IBM Portlet 和 JSR 168 Portlet 都可以使用这些服务。

  • 符合 JSR 168 规范的 Portlet 使用 JNDI 查询来检索 PortletServiceHome 对象,而该对象用于检索 Portlet 服务实现。
  • IBM Portlet 使用 PortletContext.getService() 方法检索 Portlet 服务。

WebSphere Portal V5.1.0.1 提供了数十种已准备好的 Portlet 服务。下面列出了其中一些较为常用的 Portlet 服务。有关 Portlet 服务的更多信息,请参阅 WebSphere Portal 信息中心

内容访问服务
com.ibm.portal.portlet.service.contentaccess.ContentAccessService

使 Portlet 能够访问 Internet,也使门户管理员能够集中管理此访问(例如,设置代理服务器和排除特定 Web 站点)。

凭据保险库
com.ibm.portal.portlet.service.credentialvault.CredentialVaultService

存储用户 ID 和密码,以帮助为门户用户提供单点登录体验。

动态 UI 管理器
com.ibm.portal.portlet.service.dynamicui.DynamicUIManagementFactoryService

启动动态 Portlet 和页面。

任务管理器服务
com.ibm.portal.portlet.service.taskmanager.TaskManagerDelegateFactoryService
com.ibm.portal.portlet.service.taskui.TaskUIManager

使 Portlet 能够加入与 BPEL 相关的工作流。

门户用户管理体系结构(PUMA,Portal User Management Architecture)服务
com.ibm.portal.um.portletservice.PumaHome

允许 Portlet 访问用户或组的配置文件。

Portlet 服务框架还允许开发人员编写自己的自定义 Portlet 服务并在 Portal 产品种进行注册,以供所有 Portlet 使用。





回页首


创建信息共享 Portlet 服务

示例 Portlet 服务提供了两个用于全局共享信息的方法和两个用于在用户会话内共享信息的方法。

定义接口

创建自定义 Portlet 服务的第一步是定义接口。接口声明您的 Portlet 服务提供的公共 API。

清单 1 显示了 CachedMapPortletService 的接口代码。setGlobalData()getGlobalData() 方法提供对全局(即,门户内不同用户会话间的所有 Portlet)共享数据的的支持。setSessionData()getSessionData() 方法用于在用户会话内共享数据。


清单 1. CachedMapPortletService 接口
package com.ibm.portal.sample.portletservice;
import javax.portlet.PortletSession;
import com.ibm.portal.portlet.service.PortletService;
public interface CachedMapPortletService extends PortletService
{
    public void setGlobalData(String key, Object value);
    public Object getGlobalData(String key);
    
    public void setSessionData(String key, Object value, String sessionId);
    public Object getSessionData(String key, String sessionId);
}

为了能从 IBM Portlet 访问 Portlet 服务,还需要另一个接口,因为 JSR 168 和 IBM Portlet 的 Portlet 服务扩展点不同。

清单 2 显示了使用 IBM 规范的相同 Portlet 服务的接口代码。(如果仅为 JSR 168 Portlet 编写自定义 Portlet 服务,则将不需要这个额外的接口。我们之所以需要这个特定的 Portlet 服务,因为我们希望使用它在 IBM 和 JSR 168 Portlet 之间交换信息,因此所有 Portlet 都要能够访问该 Portlet 服务。)


清单 2. CachedMapPortletServiceIBM——CachedMapPortletService 的 IBM 版本
 
package com.ibm.portal.sample.portletservice;
import org.apache.jetspeed.portlet.PortletContext;
import org.apache.jetspeed.portlet.PortletSession;
import org.apache.jetspeed.portlet.service.PortletService;
public interface CachedMapPortletServiceIBM extends PortletService
{
    public void setGlobalData(String key, Object value);
    public Object getGlobalData(String key);
    
    public void setSessionData(String key, Object value, String sessionId);
    public Object getSessionData(String key, String sessionId);
}

创建服务实现

下一步是编写服务的实现类。这个类必须实现上面定义的 Portlet 接口和 com.ibm.portal.portlet.service.spi 包中的 PortletServiceProvider 接口。


清单 3. 服务实现类
package com.ibm.portal.sample.portletservice;
import com.ibm.portal.portlet.service.spi.PortletServiceProvider;
public class CachedMapPortletServiceImpl implements CachedMapPortletService, PortletServiceProvider {
    
    private Map map = null;
    private static final String KEY_SEPARATOR = "_";
        
    // called by the portal when the service is initialized        
    public void init(Preferences servicePreferences) {
  	map = new HashMap(1000);
    }
    
    private String getSessionKey(javax.portlet.PortletSession session) {
    	return session.getId();
    }
        
    public void setGlobalData(String key, Object value) {
    	if (map == null) {
    		return;
    	} else {
    		map.put(key, value);
    	}
    }
    
    public Object getGlobalData(String key) {
    	if (map == null) {
    		return null;
    	} else {
    		return (Object) map.get(key);
    	}
    }
    
    public void setSessionData(String key, Object value, String sessionId) {
    	if (map == null || sessionId == null) {
    		return;
    	} else {
    		map.put(sessionId + KEY_SEPARATOR + key, value);
    	}
    }
    
    public Object getSessionData(String key, String sessionId) {
    	if (map == null || key == null || sessionId == null) {
    		return null;
    	} else { 
    		return (Object) map.get(sessionId + KEY_SEPARATOR + key);
    	}
    }
}

在服务的 init() 方法中,初始 HashMap 对象大小设置为 1000。此对象将承载门户中所有共享数据以及每个用户会话中使用的数据。

setSessionData()getSessionData() 方法中,会话 ID 用于确定特定用户会话的共享数据作用范围。因为 WebSphere Application Server 会始终向与特定用户会话管理的所有 Web 应用程序分配相同的会话 ID,所以可以这样做。

使 IBM Portlet 能访问服务

最后一步是让 IBM Portlet 可以访问服务实现类。为此,您需要添加 IBM 服务接口,并在其中实现所有已定义的 API。这样,就获得了一个可通过这两个接口访问的单个实现。由于 CachedMapPortletServiceIBM 和 CachedMapPortletService 接口类中定义的服务 API 完全相同,因此可以对 CachedMapPortletServiceImpl 类忽略此步骤。不过,如果您决定向服务 API 传递 Portlet 会话或 Portlet 上下文等对象,则需要为接口的 IBM 和 JSR 168 版本使用不同的方法签名。WebSphere Portal 提供了实用类 APIConverterFactory,可以使用该类来将特定对象从 IBM Portlet 包装和转换到 JSR 168 API。





回页首


向 Portlet 服务添加分布式动态缓存功能

现在已经获得了一个全功能信息共享 Portlet 服务,可以用于存储和共享全局范围和会话范围内的任意数据对象,但此服务仍然不能在生产环境中使用。正如您可能已经想到的,Portlet 服务并不会对存储的数据对象的生命周期进行管理。对象存储在服务中后,在由接收 Portlet 显式将其删除之前,它将用于保持在此处。这很容易出问题,特别对于 setSessionData() 方法更是如此,因为每个用户会话都会在服务类中创建一个条目。随着时间的增长,服务类将会增大到失控的地步,从而在生产系统中导致严重的内存争用问题。您可以利用 WebSphere Application Server 提供的动态缓存 (dynacache) 功能来管理分布式环境中的存储对象。

WebSphere Application Server 为 dynacache 提供了一个简单的接口,即 DistributedMap 接口。除了缺省 dynacache 实例外,开发人员可以分开向缓存数据对象注册和定义多个此类实例。每个实例都可以具有自身的一组属性,包括 cache JNDI name、cache size、enable disk offload、flush to disk default 和 use listener context。

首先,需要在 WebSphere Application Server 管理控制台中启用全局动态缓存服务。在缺省情况下,此服务已启用。下面列出的步骤均针对 WebSphere Application Server V5.1。V6.0 的步骤与此类似;有关更多信息,请参阅 WebSphere Application Server 信息中心

  1. 打开管理控制台。
  2. 在管理控制台导航树中选择 Servers => Application Servers
  3. 单击 WebSphere Portal server。
  4. 选择 Additional Properties => Dynamic Cache Service
  5. 在 Startup state 字段中选择 Enable service at server startup
  6. 单击 ApplyOK
  7. 重新启动 WebSphere Application Server 和 WebSphere Portal Server。

接下来,需要注册和配置供 Portlet 服务使用的新 dynacache 实例。可以采用多种方式进行此操作;在本文中,我们选择创建一个属性文件。直接在 was5_root\properties 目录下创建一个名为 distributedmap.properties 的文件(或 was6_root\properties 目录下的 cacheinstances.properties),并在其中包含以下内容。


清单 4. 分布映射属性文件
 
cache.instance.0=/services/portal/sample/cachedmap
cache.instance.0.cacheSize=1000
cache.instance.0.enableDiskOffload=false
cache.instance.0.flushToDiskOnStop=false
cache.instance.0.useListenerContext=true
cache.instance.0.enableCacheReplication=true
cache.instance.0.replicationDomain=DynaCacheCluster
cache.instance.0.disableDependencyId=true

这就创建了一个名为 cachedmap 的 DistributedMap 实例,其缓存项大小为 1,000,disk offload 已禁用,且启用了 use listener context 选项。flush to disk on stop 已禁用,并在集群环境中启用了复制操作。可以更改相应的值,以适合您的环境设置。有关这些设置的更多信息,请参阅 WebSphere Application Server V6.0 信息中心

最后,将以下代码片段添加到 Portlet 服务实现类的 init() 方法,以便使用 DistributedMap 对象。


清单 5. 服务实现类
 
public void init(Preferences servicePreferences) {
   	try {
    	InitialContext ic = new InitialContext();
   		map = (DistributedMap)ic.lookup("services/portal/sample/cachedmap");
   		System.out.println("CachedMapPortletServiceImpl - init - cache service 
   		services/portal/sample/cachedmap located successfully");
   	} catch (NamingException e) {
   		System.err.println("CachedMapPortletServiceImpl - init - can't find cache service 
   		services/portal/sample/cachedmap, creating non-cache map instead");
   		map = new HashMap(1000);
   	}
 } 

此 DistributedMap 对象同时存储全局数据和会话数据。如果有大量数据需要存储在缓存实例中,则可以考虑创建两个缓存实例和 DistributedMap 对象来分开存储库,通过这样,存在时间相对较短的会话数据就不会将存在时间长的全局数据交换出内存;而这样又避免了重新生成全局数据操作所带来的巨大开销。





回页首


注册服务

要将 CachedMapPortletService 注册到 WebSphere Portal,请执行下列操作:

  1. 如果尚未下载示例代码,请下载此实例代码
  2. 将 CachedMapPortletService.jar 放入到 wp_root/shared/app 目录中。
  3. 更新 wp_root/shared/app/config/services 目录中的 PortletServiceRegistryService.properties 文件,将以下代码添加到该文件的尾部,从而注册新服务:
      
    # CachedMapService 
    jndi:com.ibm.portal.sample.portletservice.CachedMapPortletService =
    com.ibm.portal.sample.portletservice.CachedMapPortletServiceImpl 
    com.ibm.portal.sample.portletservice.CachedMapPortletServiceIBM =
    com.ibm.portal.sample.portletservice.CachedMapPortletServiceImpl 
    

    这样就将 CachedMapPortletServiceImpl 类注册为 IBM 和 JSR 168 Portlet 服务的实现类了。

  4. 重新启动门户服务器。





回页首


访问服务

要从 IBM Portlet 访问 CachedMapPortletService,请使用以下代码:


清单 7. 从 IBM Portlet 访问服务
 
 
PortletContext context = this.getPortletConfig().getContext();
CachedMapPortletServiceIBM service = 
(CachedMapPortletServiceIBM) context.getService(CachedMapPortletServiceIBM.class);

要从 JSR 168 Portlet 访问 CachedMapPortletService,应首先在 init() 方法内获取服务主对象,因为 JNDI 查询的开销非常大,只希望进行一次此操作。


清单 8. 获取主对象,以帮助 JSR 168 进行访问
public void init(PortletConfig config) throws PortletException{
        super.init(config);
        try {
            Context ctx = new InitialContext();
            Object home =
            ctx.lookup("portletservice/com.ibm.portal.sample.portletservice.CachedMapPortletService");
            if (home != null) {
            	CachedMapPortletServiceHome = (PortletServiceHome) home;
            }
        } catch (Exception ex) {
            // can't find service home
        }
}

获取服务主对象后,就可以从 doView()processAction() 方法访问服务了。


清单 9. 从 JSR 18 Portlet 访问服务
CachedMapPortletService service = 
(CachedMapPortletService) CachedMapPortletServiceHome.getPortletService(CachedMapPortletService.class);





回页首


测试服务

现在,让我们分析以下示例,以了解可以如何使用 CachedMapPortletService 来在 Portlet 间发送和接收消息。在示例下载中,您将会发现两个示例 Portlet WAR 文件。其文件名说明了各自的用途:SampleIBM.war 包含示例 IBM Portlet,而 SampleJSR168.war 包含示例 JSR 168 Portlet。将这两个 WAR 文件安装并部署到您的 WebSphere Portal 服务器中,并将其放置到一个页面上。


图 1. 示例 IBM Portlet 和 JSR 168 Portlet——初始状态
图 1. 两个示例 Portlet——初始状态

在此示例中,可以使用示例 IBM Portlet 来向示例 JSR 168 Portlet 发送文本消息。直接在示例 IBM Portlet 的输入字段中输入一个文本字符串,然后单击 Submit,您将看到消息被接收并显示在示例 JSR 168 Portlet 中,如图 2 所示。


图 2. 示例 IBM Portlet 和 JSR 168 Portlet——发送消息
图 2. 发送了消息后的示例 Portlet

从不同的计算机或不同的浏览器会话使用不同的用户并发登录,并尝试使用此示例。您会看到,该文本消息的作用范围为用户会话;示例代码使用 setSessionData() 和 getSessionData() 方法来传输消息。如果希望所有用户会话共享某个特定消息,则可以更改代码,以转而使用 setGlobalData() 和 getGlobalDate() 方法。

尽管示例仅演示了文本 (String) 消息类型,您还可以使用 CachedMapPortletService 发送任何类型的数据对象。还可以将源 Portlet 和 目标 Portlet 部署到不同的页面上。





回页首


关于对服务进行增强

本文介绍的 Portlet 服务对 WebSphere Portal 属性代理功能形成了有用的互补。当门户包含同时运行的 IBM 和 JSR 168 Portlet,且需要彼此进行通信时,这个 Portlet 服务可提供很大帮助;不过,此 Portlet 服务并不是要成为 Portlet 消息传递代理的真正实现。与属性代码不同,此 Portlet 服务仅能用于接收 Portlet 呈现阶段的消息,而不能在 Portlet 动作阶段进行接收,因为并不能保证在发送方 Portlet 的动作(在期间合成并发送消息)之后将始终执行接收方 Portlet 的动作。

本文所讨论的 CachedMapPortletService Portlet 服务源代码包含在示例下载中。可以将其作为基础,对此 Portlet 服务进行增强,以适合您的特定需求。

例如,可以考虑添加允许锁定目标的 Portlet 消息传递的功能。该 Portlet 服务代码目前仅具有广播功能。从服务 API 中可以看出,它并不允许发送方 Portlet 指定接收方 Portlet 的名称;因此所有 Portlet(全部或属于某个用户会话,取决于所使用的 API)都能也都将收到所发出的消息。要添加目标锁定 Portlet 消息功能,可以添加额外的 API ,以将 Portlet 名称作为附加参数。而且,如果希望将 Portlet 消息的作用范围指定为 Portlet 窗口实例,可以通过指定一个唯一的 Portlet 会话键来实现。有关如何进行此操作的更多信息,请参阅 Caching data in JSR 168 portlets with WebSphere Portal V5.1 一文。

该 Portlet 的另一个可能的用法和增强是将该 Portlet 服务作为缓存代理,来存储和缓存从后端服务器计算或提取的开销很大的任何数据对象。根据用户情况不同,可以设置所缓存的数据对象由所有 Portlet 和所有最终用户全局共享,也可以将其设置为仅供每个用户会话使用。





回页首


结束语

WebSphere Portal V5.1 属性代理并不支持 IBM Portlet 和 JSR 168 portlet 之间的 Portlet 消息传递。在本文中,我们开发了一个自定义 Portlet 服务来允许 IBM 与 JSR 168 Portlet 交换信息——即彼此发送和接收消息。可以将子服务作为消极属性共享服务,以在整个门户或唯一用户会话中的 Portlet(或相同 JVM 中的任何其他 Java 类)间交换信息。我们还使用了 WebSphere Application Server 提供的动态缓存功能来缓存和管理保持在 Portlet 服务中的共享数据对象的生命周期。示例 Portlet 演示了如何使用 Portlet 服务来发送和接收消息。






回页首


下载

描述名字大小下载方法
Code samplesCachedMapPortletService_Sample.zip18 KB  FTP|HTTP
关于下载方法的信息


参考资料



作者简介

Stefan Hepper 是负责 WebSphere Portal、Workplace Client 及 Server 编程模型和公共 API 的架构师。他是 Java Portlet Specification V 1.0 (JSR 168) 的负责人之一,目前正在负责 V 2.0 (JSR 286) 的工作。Stefan 发起了 Apache Pluto 项目,该项目为 JSR 168 提供了参考实现。他曾在各种国际会议(如 JavaOne)发表演讲,曾发表了多篇论文,并且是《Pervasive Computing》(Addison-Wesley,2001 年)和《Portlets and Apache Portals》(下载手稿,Manning,2005 年)的合著者。Stefan 毕业于德国卡尔斯鲁厄大学计算机科学专业,于 1998 年加入 IBM B?blingen Development Laboratory。


Jerry Zheng 的照片

Jerry Zheng 是 WebSphere Commerce Enabled Portal 解决方案的 IBM Software Answers Network SME 的解决方案架构师。同时,他也是 Commerce Accelerator and Administrative Consoles 的 WebSphere Commerce 工具框架开发负责人。Jerry 曾获加拿大约克大学计算机科学硕士和加拿大圭尔夫大学的经济学硕士学位,并随后于 1999 年加入 IBM Toronto Lab 的 WebSphere Commerce 开发团队。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

将您的建议发给我们或者通过参加讨论与其他人分享您的想法.







回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款