首页 > 代码库 > [Java] 使用 Spring 2 Portlet MVC 框架构建 Portlet 应用

[Java] 使用 Spring 2 Portlet MVC 框架构建 Portlet 应用

转自:http://www.ibm.com/developerworks/cn/java/j-lo-spring2-portal/

 

Spring 除了支持传统的基于 Servlet 的 Web 开发之外,也支持 JSR 168 Portlet 的开发。Spring Portlet MVC 框架尽可能多地继承了 Spring Servlet MVC 框架技术,使用了相同的底层表现层抽象和整合技术。同时,由于 JSR 168 Portlet 独特的工作流程,Spring Portlet MVC 框架的使用过程较之 Spring Servlet MVC 框架,存在一些差异。并且,针对 Portlet 应用,同 Spring 1.x 相比,Spring 2.x 在 Bean 的定义方面存在特性的增强。本文的目的就是通过开发和部署一个基于 Spring 2.x 的 Portlet 应用,向读者介绍利用 Spring 2.x 进行 Portlet MVC 应用开发的要点和关键流程。

 

概述

Spring 框架包含了一个 MVC 层,提供了构建 Web 应用程序的功能。 Spring MVC 支持多种视图技术,例如 JSP、Velocity、Tiles、iText 和 POI。同时,相对于 Struts,JSF 等其它 Web 层框架,使用 Spring MVC 框架可以更充分地发挥 Spring 框架本身提供的组件装配和 AOP 的威力。

针对 JSR-168 Portlet,Spring 同样提供了一套类似的 MVC 框架。Portlet 工作流程和 Servlet 的主要差异在于,Portlet 的请求处理有两个独特的阶段:动作(Action)阶段和呈现(Render)阶段。动作阶段会有"后台"数据改变或动作的代码,这些代码只会执行一次。显示阶段会产生用户每次刷新时看到的显示内容。在单个请求的整个处理过程中,动作阶段只会被执行一次,而显示阶段可能会被执行多次。这就要求在改变系统持久状态的活动和产生显示内容的活动之间,有一个清晰的分层,Spring Portlet MVC 框架很好地体现了这一点。

相对于 Servlet 的作用域,JSR-168 Portlet 的 session 分为两种,一种是作用于整个页面并被页面上所有 Portlet 共享的 globalsession,它实质上就是 Servlet 的 session;另一种仅仅作用于单个 portlet 应用程序。针对 Web 开发,Spring 2.x 不仅新增了 request,session 两种 beanscope,而且专门针对 JSR-168 Portlet 新增了 globalSession 和 session 两种 bean scope。

本文通过开发、运行环境的搭建,以及相应的示例程序开发,向读者展示如何基于 Spring 2.x 构建 JSR-168 Portlet 应用,以及开发中需要注意到的 Spring 2.x Portlet MVC 框架特性。

在示例应用程序的开发和部署中用到了下列产品:

  • JDK 1.5 或者更高版本
  • Apache Tomcat 6.x
  • Apache Pluto 1.1.4
  • Apache Maven 2.x
  • Eclipse Europa(Eclipse V3.3) for JavaEE Developers
  • SpringFramework 2.x

回页首

JSR-168 Portlet 运行环境的搭建

SpringFramework 2.x 构建于 JDK 1.5 平台,所以我们最好使用一个支持 JDK 1.5 的 Portlet 容器实现,Apache Pluto 1.1.4 满足了这个需求,它可以用来测试我们编写的 Portlet 是否与 Portlet 规范相一致。

Apache Pluto 是 JSR-168 的参考实现,是实现了 Portlet API 的 Portlet 容器,充当 Portlet 的运行时环境,与 Web 应用服务器的 Servlet 容器的运行时环境支持 Servlet 的情形非常相似。在本文中,我们将使用 Apache Pluto 测试我们的 Portlet 应用程序。

Apache 官方提供的绑定于 Tomcat 5.x 的版本不能很好地支持 EclipseIDE 下的开发调试,所以我们需要使用 Pluto 1.1.4 的源代码从头构建一个 Portlet 容器环境。以下操作均在 WindowsXP SP2 操作系统下进行。

  1. 安装 JDK 1.5 并设定环境变量

    该步骤一般读者都比较熟悉,不再拗述。

  2. 安装 Maven 2

    Pluto 源代码使用 Maven 2 进行项目管理和构建,我们必须首先安装该工具。

    从 http://maven.apache.org/ 上寻找 Maven 2 的最新版本压缩包,下载并解压,将 bin 目录加到系统的 PATH 环境变量中。笔者所使用版本为 2.0.8,安装路径为 D:\apache-maven-2.0.8,目录结构如下。

    D:\APACHE-MAVEN-2.0.8
    ├─bin
    ├─boot
    ├─conf
    └─lib

    将 bin 目录加到系统环境变量 PATH 中。

  3. 安装 Tomcat 6

    从 http://tomcat.apache.org/ 上寻找 Tomcat 6 的最新版本压缩包,下载并解压。笔者所使用版本为 6.0.14,安装路径为 D:\apache-tomcat-6.0.14,目录结构如下:

    D:\APACHE-TOMCAT-6.0.14
    ├─bin
    ├─conf
    ├─lib
    ├─logs
    ├─temp
    ├─webapps
    └─work
  4. 下载 Apache Pluto 1.1.4 源码

    从 http://apache.mirror.phpchina.com/portals/pluto/SOURCES/v1.1.4/ 下载源码压缩包并解压,笔者解压到 D:\pluto-1.1.4,目录结构如下:

    D:\PLUTO-1.1.4
    ├─maven-pluto-plugin
    ├─pluto-ant-tasks
    ├─pluto-container
    ├─pluto-descriptor-api
    ├─pluto-descriptor-impl
    ├─pluto-portal
    ├─pluto-portal-driver
    ├─pluto-portal-driver-impl
    ├─pluto-site
    ├─pluto-site-skin
    ├─pluto-taglib
    ├─pluto-testsuite
    └─pluto-util
  5. 使用 Maven 2 构建 Pluto 1.1.4

    编辑 D:\apache-maven-2.0.8\conf 目录下的 settings.xml 文件,增加 <pluginGroups> 元素:

    <settings>
      ...
      <pluginGroups>
        <pluginGroup>org.apache.pluto</pluginGroup>
      </pluginGroups>
      ...
    </settings>

    命令行模式下进入 D:\pluto-1.1.4 目录,执行以下命令:

    D:\>cd pluto-1.1.4
    D:\pluto-1.1.4>mvn install
    D:\pluto-1.1.4>mvn pluto:install -DinstallDir=D:/apache-tomcat-6.0.14

    如果您的 Tomcat 安装路径中存在空格,则需要用双引号把路径引起来:

    mvn pluto:install -DinstallDir="C:\Program Files\Apache Software Foundation\Tomcat 6.0"

    至此,pluto 1.1.4 的相关文件就被安装到 Tomcat 相应目录下。

    编辑 D:/apache-tomcat-6.0.14/conf/tomcat-users.xml 文件,添加角色 pluto,并在该角色下新增一个用户,以下为示例文件:

    <?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>
  6. 验证安装

    运行 D:/apache-tomcat-6.0.14/bin/startup.sh,启动 Tomcat 服务器。浏览器访问 URL http://localhost:8080/pluto/portal,如 图 1 所示:

    图 1. Pluto 登录界面
    Pluto 登录界面

    输入添加到 pluto 角色的用户名和密码,进入 Pluto 的 Portal 页面:

    图 2. Pluto Portal 界面
    Pluto Portal 界面

至此,JSR-168 Portlet 运行环境 Apache Pluto 搭建成功。

 

使用 Eclipse Europa 建立开发环境

首先,需要从 Eclipse 官方网站 http://www.eclipse.org 下载 EclipseEuropa,针对不同的开发需求,有几种包可供下载。我们进行的是 J2EEWeb 开发,所以注意要下载 Eclipse IDE for Java EE Developers。

启动 Eclipse,对 Eclipse 进行配置:

执行菜单项目 Window -> Preferences,打开 Preferences 对话框,选择 Server-> InstalledRuntimes 项,如 图 3 所示:

图 3. Preferences 对话框

Preferences 对话框

点击 Add 按钮,将 Tomcat 6 添加为运行时,如 图 4、图 5 所示:

图 4. 选择运行时类型

选择运行时类型

图 5. 设定 Tomcat 安装路径

设定 Tomcat 安装路径

单击 Finish 结束配置,单击 OK 关闭 Preferences 对话框。

在 Eclipse 的 Servers 视图中单击鼠标右键,选择 New -> Server。如 图 6 所示:

图 6. 新建服务器

新建服务器

在弹出的窗口中选择目标运行服务器 Apache Tomcat 6.0 Server,运行时呈现 ApacheTomcat V6.0,如 图 7 所示,点击 Finish。

图 7. 选择目标运行服务器

选择目标运行服务器

在 Servers 视图中双击刚刚新建的 Tomcat 服务器,打开服务器配置页面,如 图 8 所示:

图 8. Tomcat 服务器配置页面

Tomcat 服务器配置页面

在 Server Locations 中选择 Use Tomcat Installation, DeployPath 选择 D:\apache-tomcat-6.0.14\webapps,如 图 9 所示。至此开发环境设置完毕。

必须设定 DeployPath 为 Tomcat 安装目录下的 webapps 目录,否则使用 Eclipse 启动 Tomcat 后,Pluto 不能加载进来。

图 9. Tomcat 服务器配置

Tomcat 服务器配置

获取 Spring 2.x

读者还可以选择从官方站点下载安装 Spring 针对 Eclipse 的 IDE 插件,以更好地支持 Spring 的开发,在本文中,该插件不是必须的。

从 http://www.springframework.org 上获取 Spring 2.x 的最新版本,笔者下载为 SpringFramework 2.5.1,建议下载 spring-framework-2.5.1-with-dependencies.zip,这样就不需要另外寻找其它所依赖 Jar 包。将压缩包解压。

 

Spring Portlet MVC 框架简介

这个框架是围绕着分发器 DispatcherPortlet 设计的,分发器把请求转发给处理器。和 Web 框架的 DispatcherServlet 一样,这个框架还有可配置的处理器映射和视图解析,同时也支持文件上传。

MVC 分层结构

Spring Portlet MVC 框架存在控制器(Controller)和视图(View):

  1. 控制器 C

    缺省的处理器是一个非常简单的 Controller 接口,它提供了两个方法来处理 Portlet 请求的两个阶段:动作请求和显示请求:

    • void handleActionRequest(request,response) //动作阶段处理动作请求
    • ModelAndView handleRenderRequest(request,response) //显示阶段应该处理显示请求,并返回合适的模型和视图

    这个框架包含了许多相同的控制器实现层次,比如,AbstractController,SimpleFormController等。它在数据绑定、命令对象使用、 模型处理和视图解析等方面和 Servlet 框架相同。

  2. 视图 V

    这个框架利用了一个特殊的桥 Servlet ViewRendererServlet 来使用 Servlet 框架里的视图显示功能,这样,Portlet 请求就被转化为 Servlet 请求,Portlet 视图能够以通常的 Servlet 底层代码来显示。这意味着,在 Portlet 里仍能使用当前所有的显示方法,如 JSP、Velocity 等。

Web 作用范围的 Bean

Spring Portlet MVC 支持 Web Bean,这些 Bean 的生命周期存在于当前的 HTTP 请求或 HTTP Session(一般的和全局的)里,容器的 WebApplicationContext 提供。

相对于 Spring 1.x,Spring 2.x 新增了以下针对于 Web Bean 的作用域:

  • Request 作用域

    注意以下 bean 定义:

    <bean id="loginAction" class="test.LoginAction" scope="request"/>

    针对每次 HTTP 请求,Spring 容器都会创建一个全新的 loginAction 实例,且该实例仅在当前 HTTP 请求内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request 作用域的 bean 实例将被销毁。

  • Session作用域

    注意以下 bean 定义:

    <bean id="userPreferences" class="test.UserPreferences" scope="session"/>

    针对某个 HTTP Session,Spring 容器会创建一个全新的 userPreferences 实例,且仅在当前 HTTP Session 内有效。与 request 作用域一样,您可以根据需要放心的更改所创建实例的内部状态,而别的 HTTP Session 中创建的实例,将不会看到这些特定于某个 HTTP Session 的状态变化。当 HTTP Session 结束后,在该 HTTP Session 作用域内的 bean 将被销毁。

    在 Portlet 中,这类 Bean 的作用域是单个 portlet 的 session。

  • global session 作用域

    注意以下 bean 定义:

    <bean id="userPreferences" class="test.UserPreferences" scope="globalSession"/>

    global session 作用域类似于标准的 HTTP Session 作用域,不过它仅仅在基于 portlet 的 Web 应用中才有意义。Portlet 规范定义了全局 Session 的概念,它被所有构成某个 portlet web 应用的所有 portlet 所共享。在 global session 作用域中定义的 bean 被限定于全局 Portlet Session 的生命周期范围内。

DispatcherPortlet

Portlet MVC 是一个请求驱动的框架,它围绕着 Portlet 设计,把请求转发给控制器,提供了便利的 Porltet 应用开发功能。

和一般的 Portlet 一样,需要在 Portlet Web 应用的 portlet.xml 中声明:

<portlet>
	<portlet-name>test</portlet-name>
	<portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>
	<supports>
		<mime-type>text/html</mime-type>
		<portlet-mode>view</portlet-mode>
	</supports>
	<portlet-info>
		<title>Test Portlet</title>
	</portlet-info>
</portlet>

在 PortletMVC 框架里,每个 DispatcherPortlet 都有自己的 WebApplicationContext,它不仅接管了所有在根 WebApplicationContext 注册的 Bean,而且还可以在 Portlet 作用范围内对这些 Bean 进行重载,重载后的 Bean 可以定义成对于特定的 Portlet 实例可见。在初始化 DispatcherPortlet 时,框架会在 Web 应用的 WEB-INF 目录下寻找 [portlet-name]-portlet.xml,将其中定义的 Bean 注册到自己的 WebApplicationContext 上。

ViewRendererServlet

为了复用所有 Spring WebMVC 里的视图技术,必须把 PortletRequest/PortletResponse 转换到 HttpServletRequest/HttpServletResponse,然后调用 View 的 render 方法。为此,DispatcherPortlet 使用了一个特殊的 servlet:ViewRendererServlet。

必须在 web.xml 文件里为您的 web 应用声明一个 ViewRendererServlet 的实例:

<servlet>
    <servlet-name>ViewRendererServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.ViewRendererServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>ViewRendererServlet</servlet-name>
    <url-pattern>/WEB-INF/servlet/view</url-pattern>
</servlet-mapping>
处理器映射

通过处理器映射,可以把 portlet 请求对应到合适的处理器上。在 Spring WebMVC 里,处理器映射通常是基于 URL 的。但是在 Portlet 里没有 URL,必须使用其它的机制来控制映射。最常见的是根据 portlet 模式和请求参数,但在 portlet 请求里的任何对象都可以用在自定义的处理器映射中。

根据 portlet 模式和请求参数,主要有下列处理器映射可用:

  • PortletModeHandlerMapping

    基于当前的 portlet 模式(比如:‘view‘, ‘edit‘, ‘help‘)。如下:

    <bean id="portletModeHandlerMapping"
          class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
        <property name="portletModeMap">
    	    <map>
                <entry key="view" value-ref="viewHandler"/>
                <entry key="edit" value-ref="editHandler"/>
                <entry key="help" value-ref="helpHandler"/>
            </map>
        </property>
    </bean>
  • ParameterHandlerMapping

    在不改变 portlet 模式的情况下在多个控制器间切换,把一个请求参数作为 key 来控制映射。

    ParameterHandlerMapping 使用一个特定的请求参数来控制映射。这个参数的缺省名是 ‘action‘,可以通过 ‘parameterName‘ 属性来改变。

    Bean设置如下:

    <bean id="parameterHandlerMapping"
    class="org.springframework.web.portlet.handler.ParameterHandlerMapping"/>
        <property name="parameterMap">
            <map>
                <entry key="add" value-ref="addItemHandler"/>
                <entry key="edit" value-ref="editItemHandler"/>
                <entry key="delete" value-ref="deleteItemHandler"/>
            </map>
        </property>
    </bean>
  • PortletModeParameterHandlerMapping

    PortletModeParameterHandlerMapping 结合了前两者的功能,如下:

    <bean id="portletModeParameterHandlerMapping"
    class="org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping">
        <property name="portletModeParameterMap">
            <map>
                <entry key="view"><!-- ‘view‘ portlet模式 -->
                    <map>
                        <entry key="add" value-ref="addItemHandler"/>
                        <entry key="edit" value-ref="editItemHandler"/>
                        <entry key="delete" value-ref="deleteItemHandler"/>
                    </map>
                </entry>
                <entry key="edit"><!-- ‘edit‘ portlet模式 --> 
                    <map>
                        <entry key="prefs" value-ref="prefsHandler"/>
                        <entry key="resetPrefs" value-ref="resetPrefsHandler"/>
                    </map>
                </entry>
            </map>
        </property>
    </bean>
异常处理

和 Web MVC 一样,Portlet MVC 提供了 HandlerExceptionResolver 来处理异常,PortletMVC 同样也提供了具体的 SimpleMappingExceptionResolver,可以将抛出的异常对应到一个视图名。

 

创建基于 Spring 2.x 的 Portlet 应用

下面,我们开始创建一个示例程序来进行演示,该示例的主要功能是:

  • 在 View 模式下显示一个表单,要求用户输入地址数据,点击提交按钮呈现新页面,该页面显示刚提交的数据内容。
  • Edit 模式下显示资源不可用,提供返回 View 模式的超链接。
  • Help 模式下显示资源不可用,提供返回 View 模式的超链接。

其中,后两者体现了 Spring Portlet MVC 对异常的处理能力。

为了帮助读者理解,本文提供了完整的样例代码下载,包含该部分的所有代码。

在本文中,使用 Spring 2.x 开发 Portlet 应用需要经历以下步骤:

  1. 使用 Eclipse 创建 Java EE Web 项目
  2. 编辑 context.xml 文件
  3. 编辑 web.xml 文件
  4. 编辑 portlet.xml 文件
  5. 生成 Java 类、JSP 文件以及相关 Spring 配置文件
  6. 部署 Portlet 应用程序

 

一、使用 Eclipse 创建 Java EE Web 项目

新建项目,项目类型选择Web -> Dynamic Web Project,如 图 10 所示:

图 10. 新建动态 Web 项目

新建动态 Web 项目

接下来,设置项目属性,项目名称 springportal, 目标运行时 Apache Tomcat V6.0,保留默认设置,点击 Finish,如 图 11 所示:

图 11. 设置项目属性

设置项目属性

生成项目结构如 图 12:

Apache Pluto 官方提供的 bundle 版本使用的 Tomcat 版本是 5.5,其目录结构和 Tomcat 6 有所差异,使用 Eclipse 开发时,IDE 不能自动将 pluto 所需的所有 Jar 文件加载到工程的 lib 里,从而使所建 Web 工程的 CLASSPATH 缺失所需类。这就是我们使用 Pluto 源码从头构建 Pluto Portlet 容器的原因。

图 12. 项目结构

项目结构

从解压后的 spring 文件夹中寻找 jstl.jar、standard.jar、spring.jar、spring-webmvc.jar、spring-webmvc-portlet.jar 拷贝到 WEB-INF/lib 目录下。

 

二、编辑 context.xml 文件

在 META-INF 下新建 context.xml 文件,内容如下:

<Context path="springportal" docBase="springportal" crossContext="true" />

该文件为 Tomcat的 特有配置文件,根据 Pluto 的要求,该 Web 工程的上下文应该可以被其它 JavaEE 程序访问,所以 crossContext 参数设置为 true。

 

三、编辑 web.xml 文件

编辑 web.xml 文件,如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">

	<display-name>springportal</display-name>

	<!-- 设定Spring的根上下文 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/applicationContext.xml</param-value>
	</context-param>

	<listener>
		<listener-class>
			org.springframework.web.context.ContextLoaderListener
		</listener-class>
	</listener>

	<!-- 设定ViewRendererServlet -->
	<servlet>
		<servlet-name>ViewRendererServlet</servlet-name>
		<servlet-class>
			org.springframework.web.servlet.ViewRendererServlet
		</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>ViewRendererServlet</servlet-name>
		<url-pattern>/WEB-INF/servlet/view</url-pattern>
	</servlet-mapping>
	
    <!-- 设定加载一个Portlet的Servlet, 该配置为Pluto所需-->
	<servlet>
		<servlet-name>SpringTestPortlet1</servlet-name>
		<servlet-class>
			org.apache.pluto.core.PortletServlet
		</servlet-class>
		<init-param>
			<param-name>portlet-name</param-name>
			<param-value>SpringTestPortlet1</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>SpringTestPortlet1</servlet-name>
		<url-pattern>/PlutoInvoker/SpringTestPortlet1</url-pattern>
	</servlet-mapping>
</web-app>

读者可自行查询 Pluto 的参考手册,但是,官方提供的文档不仅不够完善,而且存在错误的描述,希望读者在参考 Pluto 文档的时候能够多加注意。

注意代码段粗体字后面的部分,该部分配置为 Pluto 所需。通过该配置 Pluto 将每个 Portlet 映射为一个 Servlet,通过 Servlet 将 Portlet 呈现并发布出去。为了让 Pluto 管理界面查找到 Portlet,必须使 Servlet 在使用管理界面之前实例化,所以我们配置了 load-on-startup 参数。该参数仅仅是一个建议,不是必需的。如果读者通过更改 Pluto 的页面布局配置文件包含 Portlet,则不需要配置该参数。

 

四、编辑 portlet.xml 文件

在 WEB-INF 目录下新建 portlet.xml:

<?xml version="1.0" encoding="UTF-8"?>
<portlet-app
	xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
	version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd 
	http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd">
	<portlet>
		<portlet-name>SpringTestPortlet1</portlet-name>
		<display-name>SpringTestPortlet1</display-name>
		<portlet-class>
			org.springframework.web.portlet.DispatcherPortlet
		</portlet-class>
		<init-param>
			<name>contextConfigLocation</name>
			<value>/WEB-INF/springtest-portlet1.xml</value>
		</init-param>
		<supports>
			<mime-type>text/html</mime-type>
			<portlet-mode>view</portlet-mode>
			<portlet-mode>edit</portlet-mode>
			<portlet-mode>help</portlet-mode>
		</supports>
		<portlet-info>
			<title>SpringTestPortlet1</title>
		</portlet-info>
	</portlet>
</portlet-app>

该配置文件定义了一个名为 SpringTestPortlet1 的 portlet,支持 view、edit、help 三种模式,并且 Spring 的上下文配置文件为 WEB-INF 目录下的 springtest-portlet1.xml 文件,该文件中配置的 bean 只对这个 portlet 可见。

 

五、生成 Java 类、JSP 文件以及相关 Spring 配置文件

Java 类

本示例需要一个 command 类来存储用户输入的表单数据信息:

AddressBook.java
package springportal.command;

public class AddressBook {
	private String name;

	private String address;

	private String telphone;

	private String mobile;

	private String email;

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getMobile() {
		return mobile;
	}

	public void setMobile(String mobile) {
		this.mobile = mobile;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getTelphone() {
		return telphone;
	}

	public void setTelphone(String telphone) {
		this.telphone = telphone;
	}
}
JSP 文件

在 WEB-INF 目录下新建 jsp 目录,分别新建以下 Jsp 文件:

addressInput.jsp 地址输入表单
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<portlet:actionURL var="actionURL" />
<form:form commandName="addressBook" action="${actionURL}">
	<table>
		<tr>
			<td>Name:<c:out value="http://www.mamicode.com/${addressBook.name}" /></td>
			<td><form:input path="name" /></td>
		</tr>
		<tr>
			<td>Address:</td>
			<td><form:input path="address" /></td>
		</tr>
		<tr>
			<td>Telphone:</td>
			<td><form:input path="telphone" /></td>
		</tr>
		<tr>
			<td>Mobile:</td>
			<td><form:input path="mobile" /></td>
		</tr>
		<tr>
			<td>Email:</td>
			<td><form:input path="email" /></td>
		</tr>
		<tr>
			<td colspan="2"><input type="submit" value="http://www.mamicode.com/submit" /></td>
		</tr>
	</table>
</form:form>
result.jsp 显示地址输入结果
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<table>
	<tr>
		<td>Name:</td>
		<td><c:out value="http://www.mamicode.com/${addressBook.name}" /></td>
	</tr>
	<tr>
		<td>Address:</td>
		<td><c:out value="http://www.mamicode.com/${addressBook.address}" /></td>
	</tr>
	<tr>
		<td>Telphone:</td>
		<td><c:out value="http://www.mamicode.com/${addressBook.telphone}" /></td>
	</tr>
	<tr>
		<td>Mobile:</td>
		<td><c:out value="http://www.mamicode.com/${addressBook.mobile}" /></td>
	</tr>
	<tr>
		<td>Email:</td>
		<td><c:out value="http://www.mamicode.com/${addressBook.email}" /></td>
	</tr>
	<tr>
		<td colspan="2"><a
		href="http://www.mamicode.com/
unavailable.jsp 显示资源不可用页面
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<h2>not availiable</h2>
<a href="http://www.mamicode.com/
error.jsp 错误异常处理页面
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<h3>An Error Occurred</h3>
message:<c:out value="http://www.mamicode.com/${exception.message}" />
<a href="http://www.mamicode.com/
Spring 配置文件

在 WEB-INF 下新建 applicationContext.xml 文件,内容如下:

applicationContext.xml Spring Web 应用程序根上下文配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- Default View Resolver -->
	<bean id="viewResolver"
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="viewClass"
			value="http://www.mamicode.com/org.springframework.web.servlet.view.JstlView" />
		<property name="prefix" value="http://www.mamicode.com/WEB-INF/jsp/" />
		<property name="suffix" value="http://www.mamicode.com/.jsp" />
	</bean>

	<!-- Default ExceptionHandler -->
	<bean id="defaultExceptionHandler"
	class="org.springframework.web.portlet.handler.SimpleMappingExceptionResolver">
		<property name="order" value="http://www.mamicode.com/10" />
		<property name="defaultErrorView" value="http://www.mamicode.com/error" />
		<property name="exceptionMappings">
			<props>
			<prop key="javax.portlet.UnavailableException">	unavailable</prop>
			<prop key="java.lang.Exception">error</prop>
			</props>
		</property>
	</bean>

</beans>

该配置文件中,定义了两个 Bean。其中第一个 Bean 定义了视图的默认解析方式,使用 JSP 作为视图(View),到 /WEB-INF/jsp/ 目录下寻找 Jsp 文件,并且视图名称为 jsp 文件名的前缀。

在第二个 Bean 中,定义了异常处理方式。如果发生 javax.portlet.UnavailableException 异常,则呈现 unavailable 视图,即 unavailable.jsp 文件;如果发生其它的 java.lang.Exception 异常,则呈现 error 视图,即 error.jsp 文件。

在 WEB-INF 下新建 PortletSpringTestPortlet1 的 Spring 上下文配置文件 springtest-portlet1.xml 如下:

springtest-portlet1.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean
		class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
		<property name="portletModeMap">
			<map><entry key="view" value-ref="viewController" />
			</map>
		</property>
	</bean>

	<bean id="viewController"
		class="org.springframework.web.portlet.mvc.SimpleFormController">
		<property name="commandClass"
			value="http://www.mamicode.com/springportal.command.AddressBook" />
		<property name="commandName" value="http://www.mamicode.com/addressBook" />
		<property name="formView" value="http://www.mamicode.com/addressInput" />
		<property name="successView" value="http://www.mamicode.com/result" />
	</bean>

</beans>

该上下文根据 Portlet 模式分配控制器,由 Bean 定义可知,仅仅定义了 view 模式的控制器,edit 和 help 模式都没有进行定义。

在 View 模式的控制器 viewController 中,定义了 commandClass 和 commandName 来保存用户输入的表单数据,addressInput 视图(addressInput.jsp)为输入表单,result 视图(result.jsp)呈现表单提交结果。

 

六、部署 Portlet 应用程序

在 Eclipse 的 Servers 视图中,右键单击 Tomcat 服务器,点击 Add and RemoveProjects,在弹出的对话框中将 springportal 项目添加到右侧栏目中,点击 Finish 确认,如 图 13:

图 13. 部署项目到服务器

部署项目到服务器

在 Servers 视图中启动服务器,Console 视图中输出 Tomcat 启动信息。

浏览器中输入相应 url 访问 pluto 的 portal 页面,登录,点击 Pluto Admin 导航栏,如 图 14:

图 14. Pluto 管理界面

Pluto 管理界面

使用该工具新建一个 page,如 "Test Spring PortletPage"。导航栏中马上新增一项 "Test Spring PortletPage",点击进入该页面可以见到目前该页面没有内容。

选择 springportal 应用程序中的 PortletSpringTestPortlet1,选择上一步新建的页面,点击 AddPortlet 按钮,将 SpringTestPortlet1 布局到 "Test Spring PortletPage" 页面。如 图 15:

图 15. 布局 Portlet 到 Portal 页面

布局 Portlet 到 Portal 页面

进入 "Test Spring PortletPage" 页面,该页面新增了一个 Portlet,目前显示的是 View 模式下 addressInput 视图,如 图 16:

图 16. Portlet 界面 addressInput 视图

Portlet 界面

输入一些信息,点击 Submit 按钮,跳转到 result 视图,如 图 17:

图 17. result 视图

result 视图

点击铅笔图标进入 Edit 模式,由于未给该模式配置控制器,Pluto 容器抛出异常 javax.portlet.UnavailableException,被 Spring 捕获,转到 unavailable 视图,如 图 18:

图 18. Edit 模式

Edit 模式

点击问号图标进入 Help 模式,由于未给该模式配置控制器,同样转到 unavailable 视图。

至此,我们已经成功部署了一个 Portlet。

 

使用 Web Bean

接下来,我们通过一个示例来验证 WebBean 的三个作用域:request、session、globalSession。

Java 代码

新增一个 Java 类 TestBean:

TestBean.java
package springportal.bean;

import java.util.Date;

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;

public class TestBean implements InitializingBean, BeanNameAware {

	private String beanName;

	private Date initializeTime;

	public Date getInitializeTime() {
		return initializeTime;
	}

	public void setInitializeTime(Date initializeTime) {
		this.initializeTime = initializeTime;
	}

	public void afterPropertiesSet() throws Exception {
	setInitializeTime(new Date());
	System.out.println("bean [" + beanName + "] was initialized at "+ initializeTime);
	}

	public void setBeanName(String beanName) {
		this.beanName = beanName;
	}
}

其中 InitializingBean 和 BeanNameAware 为 Spring 的回调接口,Spring 上下文加载过程中使用 setBeanName 方法将 Bean 的名称(XML 配置中 id 或者 name 属性)注入到 Bean 的属性中,并且在 Bean 初始化完成后调用 afterPropertiesSet 方法,该 Bean 将在控制台打印自己的初始化时刻。

新建一个控制器类 TestScopeController,该类将从 Spring 上下文中获取的 Bean 示例存放到视图 testScope 中。

TestScopeController.java
package springportal.mvc;

import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.mvc.AbstractController;

import springportal.bean.TestBean;

public class TestScopeController extends AbstractController {

    @Override
    protected ModelAndView handleRenderRequestInternal(RenderRequest request,
    RenderResponse response) throws Exception {
	
        ModelAndView mav = new ModelAndView("testScope");
        TestBean requestTestBean = 
            (TestBean) getApplicationContext().getBean("requestTestBean");
        TestBean sessionTestBean = 
            (TestBean) getApplicationContext().getBean("sessionTestBean");
        TestBean globalSessionTestBean = 
            (TestBean) getApplicationContext().getBean("globalSessionTestBean");
        mav.addObject("requestTestBean", requestTestBean);
        mav.addObject("sessionTestBean", sessionTestBean);
        mav.addObject("globalSessionTestBean", globalSessionTestBean);
        return mav;
	}
}
JSP 文件

在 WEB-INF/jsp/ 目录下新建 testScope.jsp 文件,展示 Bean 的初始化时间。

testScope.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
The initialize time of request scope testBean:
<c:out value="http://www.mamicode.com/${requestTestBean.initializeTime}" />
<br />
The initialize time of session scope testBean:
<c:out value="http://www.mamicode.com/${sessionTestBean.initializeTime}" />
<br />
The initialize time of global session scope testBean:
<c:out value="http://www.mamicode.com/${globalSessionTestBean.initializeTime}" />
编辑 web.xml 和 portlet.xml 文件

编辑 web.xml 文件,新增两个 portlet 的 servlet 定义,分别为 SpringTestPortlet2 和 SpringTestPortlet3,其配置与 SpringTestPortlet1 相同,不再拗述。

编辑 portlet.xml 文件,新增两个 portlet 定义,分别为 SpringTestPortlet2 和 SpringTestPortlet3,这两个 portlet 只支持 View 模式,并共用一个 Spring 配置文件 springtest-portlet2.xml。实际上,这两个 Portlet 的内容完全是一样的。如下:

...	<portlet>
		<portlet-name>SpringTestPortlet2</portlet-name>
		<display-name>SpringTestPortlet2</display-name>
		<portlet-class>
			org.springframework.web.portlet.DispatcherPortlet
		</portlet-class>
		<init-param>
			<name>contextConfigLocation</name>
			<value>/WEB-INF/springtest-portlet2.xml</value>
		</init-param>
		<supports>
			<mime-type>text/html</mime-type>
			<portlet-mode>view</portlet-mode>
		</supports>
		<portlet-info>
			<title>SpringTestPortlet2</title>
		</portlet-info>
	</portlet>

	<portlet>
		<portlet-name>SpringTestPortlet3</portlet-name>
		<display-name>SpringTestPortlet3</display-name>
		<portlet-class>
			org.springframework.web.portlet.DispatcherPortlet
		</portlet-class>
		<init-param>
			<name>contextConfigLocation</name>
			<value>/WEB-INF/springtest-portlet2.xml</value>
		</init-param>
		<supports>
			<mime-type>text/html</mime-type>
			<portlet-mode>view</portlet-mode>
		</supports>
		<portlet-info>
			<title>SpringTestPortlet3</title>
		</portlet-info>
	</portlet>
...
Spring 配置文件

在 WEB-INF 下新建 Spring 配置文件 springtest-portlet2.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- the request scope bean -->
	<bean id="requestTestBean" class="springportal.bean.TestBean"
		scope="request" />

	<!-- the session scope bean -->
	<bean id="sessionTestBean" class="springportal.bean.TestBean"
		scope="session" />

	<!-- the global session scope bean -->
	<bean id="globalSessionTestBean" class="springportal.bean.TestBean"
		scope="globalSession" />

	<bean
		class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
		<property name="portletModeMap">
			<map><entry key="view" value-ref="viewController" />
			</map>
		</property>
	</bean>

	<bean id="viewController"
		class="springportal.mvc.TestScopeController" />
</beans>

在该配置文件中,分别用相同的 TestBean 类配置了三个不同 scope 的 Bean。

部署 Portlet

启动 Tomcat,登录 Pluto,新建页面 "Test Bean ScopePage",并将 SpringTestPortlet2 布局到这个页面上。进入这个页面,如 图 19:

图 19. SpringTestPortlet2

SpringTestPortlet2

注意到 Console 视图中出现以下日志信息,表示三个 Bean 都初始化了一次:

...其它日志信息...
bean [requestTestBean] was initialized at Wed Feb 20 14:18:00 CST 2008
bean [sessionTestBean] was initialized at Wed Feb 20 14:18:00 CST 2008
bean [globalSessionTestBean] was initialized at Wed Feb 20 14:18:00 CST 2008
...其它日志信息...

刷新这个页面,Console 视图中出现以下日志信息:

...其它日志信息...
bean [requestTestBean] was initialized at Wed Feb 20 14:20:52 CST 2008
...其它日志信息...

可以看到,portlet 中 request 作用域的 bean 又重新初始化一次,但 session 和 globalSession 作用域的 bean 没有重新初始化。

将 SpringTestPortlet3 布局到页面 "Test Bean ScopePage" 上。进入这个页面。Console 视图中出现以下日志信息:

...其它日志信息...
bean [requestTestBean] was initialized at Wed Feb 20 14:23:40 CST 2008
...其它日志信息...
bean [requestTestBean] was initialized at Wed Feb 20 14:23:40 CST 2008
bean [sessionTestBean] was initialized at Wed Feb 20 14:23:40 CST 2008
...其它日志信息...

可见,每 portlet 中 request 作用域的 bean 又重新初始化一次,但只有 SpringTestPortlet3 重新初始化了一个 session 作用域的 Bean,但 globalSession 作用域的 bean 没有重新初始化。

笔者同时也可以观看 portal 页面体会 Web Bean 的三种不同作用域。

 

结束语

本文讨论了如何使用 Spring 2.x Porltet MVC Framework 在 Apache Pluto 上来开发和部署 Portlet 应用,以及 Spring Portlet MVC 开发中的一些有趣的特性。按照本文讲述的步骤操作,就可以在 Apache Pluto 上建立一个比较完整的 Portlet 应用了。读者可以自行将示例代码移植到支持 JDK 1.5 和 JSR-168 规范的其它 Portlet 容器中。

 

本文用到的 Perl 脚本示例
springportal.zip
12 KB