16.4. 控制器

Portlet MVC里的控制器和Web MVC的很相似,在两者之间移植代码很简单。

Portlet MVC控制器构架的基础是 org.springframework.web.portlet.mvc.Controller 接口,如下所示。

public interface Controller {

    /**
     * Process the render request and return a ModelAndView object which the
     * DispatcherPortlet will render.
     */
    ModelAndView handleRenderRequest(RenderRequest request, RenderResponse response)
        throws Exception;

    /**
     * Process the action request. There is nothing to return.
     */
    void handleActionRequest(ActionRequest request, ActionResponse response)
        throws Exception;

}

如你所见,Portlet Controller接口需要两个方法来处理Portlet 请求的两个阶段:动作请求和显示请求。动作阶段应该能够处理动作请求,显示阶段应该 能够处理显示请求,并返回合适的模型和视图。 尽管Controller接口是抽象的,但Spring Portlet MVC 提供了很多包含了各种各样你需要的功能的控制器-它们中的大多数和Spring Web MVC里的控制器很类似。 Controller接口只定义每个控制器需要的通用的功能 - 处理动作请求,处理显示请求,返回模型和视图。

16.4.1. AbstractControllerPortletContentGenerator

当然,仅一个Controller 是不够的。为了提供基本的功能,所有的Spring Portlet ControllerAbstractController继承,后者可以访问Spring 的ApplicationContext和控制缓存。

表 16.3. AbstractController提供的功能

参数 解释
requireSession 表明当前的 Controller是否需要session。 所有的控制器都能使用这个功能。如果这样的控制器收到请求时, session不存在,用户会收到 SessionRequiredException异常。
synchronizeSession 如果需要控制器在处理用户session时保持同步,使用 这个参数。更具体来说,扩展的控制器会覆盖handleRenderRequestInternal(..)handleActionRequestInternal(..)方法,如果指定了这个参数, 这两个方法会在处理用户session时保持同步。
renderWhenMinimized 如果需要在portlet最小化状态时,控制器也显示视图, 把这个参数设为true。这个参数缺省是false,所以portlet在最小化状态 时,不显示内容。
cacheSeconds 在需要控制器覆盖当前portlet定义的缺省缓存失效时间时, 设置一个正的整数。这个参数缺省是-1, 表示不改变缺省的缓存,把它设为0,就是 确保不缓存结果。

requireSessioncacheSeconds属性是在 AbstractController的父类 PortletContentGenerator里声明的。为了完整性, 把它们列在这里。

在你自己的控制器里继承AbstractController时 (不推荐这样做,因为已经有许多现成的控制器,它们可能有你需要的功能),仅需要覆盖 handleActionRequestInternal(ActionRequest, ActionResponse)方法或 handleRenderRequestInternal(RenderRequest, RenderResponse)方法(或两者都覆盖),实现逻辑, 并返回 ModelAndView 对象(如果是 handleRenderRequestInternal方法)。

handleActionRequestInternal(..)handleRenderRequestInternal(..)方法的缺省实现都会 抛出PortletException,这和JSR-168规范API里的 GenericPortlet的行为是一致的。所以只要覆盖你的控制器 需要处理的方法。

下面简短的例子包含了一个类和一个在web应用context里的声明。

package samples;

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

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

public class SampleController extends AbstractController {

    public ModelAndView handleRenderRequestInternal(
        RenderRequest request,
        RenderResponse response) throws Exception {

        ModelAndView mav = new ModelAndView("foo");
        mav.addObject("message", "Hello World!");
        return mav;
    }
}

<bean id="sampleController" class="samples.SampleController">
    <property name="cacheSeconds" value="120"/>
</bean>

为了使得一个简单的控制器工作,你只需要类似上面的类和在web应用context里的声明, 并且再设置一下处理器映射(见 第 16.5 节 “处理器映射”)。

16.4.2. 其它简单的控制器

尽管你能够继承AbstractController, Spring Portlet MVC提供了不少具体的实现,它们提供了许多在简单MVC应用里常用的功能。

ParameterizableViewController基本上 和上面的例子类似,除了你能指定web应用context返回的视图的名字。(不需要写死视图名称)。

PortletModeNameViewController把当前的 Portlet的状态作为视图名,如果Portlet在View模式 (比如:PortletMode.VIEW),这个“View”就是视图名。

16.4.3. Command控制器

Spring Portlet MVC提供了和Spring Web MVC完全一致的 command controllers层次结构,提供方法来与数据对象交互 并且动态地把参数从PortletRequest 绑定到数据对象上。数据对象不需要实现框架相关的接口,因而你可以 直接操作这些持久化对象。下面让我们查看Command控制器提供的功能,来了解它们的使用:

  • AbstractCommandController - Command控制器,可以用来创建自己的控制器,它能够将请求里的参数 绑定到指定的数据对象。这个类不提供表单功能,但它提供验证功能,并且 可以在控制器里指定如何处理带有请求参数的Command对象。

  • AbstractFormController - 提供表单提交支持的抽象控制器。你能够对表单进行建模,通过从控制器 里得到的Command对象来填充表单。在用户提交表单后, AbstractFormController会绑定字段、进行验证, 然后把对象返回给控制器来做下一步的动作。支持的功能有:无效表单提交(重新 提交)、验正和通常的表单流程。你需要实现方法来决定表单的显示和成功时使用的 视图。如果你需要表单,但不想在应用context里指定用户看到的视图,使用这个 控制器。

  • SimpleFormController - 一个具体的AbstractFormController, 对使用对应的command对象生成表单提供了更多的支持。 SimpleFormController可以让你在用户成功地提交 表单或其它状态时,指定command对象,表单的视图名以及页面对应的视图名。

  • AbstractWizardFormController – 具体的AbstractFormController,它提交了向导式的接口 来编辑跨多个页面的command对象。支持多种用户动作:完成、取消或者页面变化,所有这些 都可以简便地在视图的请求参数里指定。

这些command控制器是非常强大的,为了有效地使用,需要对它们的原理有 细致的理解。在你开始使用它们前,务必仔细阅读它们层次结构的javadoc以及示例。

16.4.4. PortletWrappingController

除了开发新的控制器,我们可以重用现有的portlet并且在 DispatcherPortlet 把请求映射指向它们。通过PortletWrappingController,你能实例化一个 现有的Portlet来作 Controller,如下所示:

<bean id="wrappingController"
      class="org.springframework.web.portlet.mvc.PortletWrappingController">
    <property name="portletClass" value="sample.MyPortlet"/>
    <property name="portletName" value="my-portlet"/>
    <property name="initParameters">
        <value>
            config=/WEB-INF/my-portlet-config.xml
        </value>
    </property>
</bean>

这会很有价值,因为可以使用拦截器来对送向这些portlet的请求进行预处理和后处理。 而且也很方便,因为JSR-168没有提供对过滤机制的支持。比如,可以在一个MyFaces JSR Portlet外面加上Hibernate的 OpenSessionInViewInterceptor