13.3. 控制器

控制器的概念是MVC设计模式的一部分(确切地说,是MVC中的C)。应用程序的行为通常被定义为服务接口,而控制器使得用户可以访问应用所提供的服务。控制器解析用户输入,并将其转换成合理的模型数据,从而可以进一步由视图展示给用户。Spring以一种抽象的方式实现了控制器概念,这样使得不同类型的控制器可以被创建。Spring本身包含表单控制器、命令控制器、向导型控制器等多种多样的控制器。

Spring控制器架构的基础是org.springframework.mvc.Controller接口,其代码如下:

public interface Controller {

    /**
     * Process the request and return a ModelAndView object which the DispatcherServlet
     * will render.
     */
    ModelAndView handleRequest(
        HttpServletRequest request,
        HttpServletResponse response) throws Exception;

}

你可以发现Controller接口仅仅声明了一个方法,它负责处理请求并返回合适的模型和视图。Spring MVC实现的基础就是这三个概念:Mdel、View(ModelAndView)以及 Controller。虽然Controller接口是完全抽象的,但Spring也提供了许多你可能会用到的控制器。Controller接口仅仅定义了每个控制器都必须提供的基本功能:处理请求并返回一个模型和一个视图。

13.3.1. AbstractControllerWebContentGenerator

为了提供一套基础设施,所有的Spring控制器都继承了 AbstractControllerAbstractController 提供了诸如缓存支持和mimetype设置这样的功能。

表 13.3. AbstractController提供的功能

功能 描述
supportedMethods 指定这个控制器应该接受什么样的请求方法。通常它被设置成同时支持GET和POST,但是你可以选择你想支持的方法。如果控制器不支持请求发送的方法,客户端会得到通知(通常是抛出一个ServletException)。
requiresSession 指定这个控制器是否需要HTTP session才能正常工作。如果控制器在没有session的情况下接收到请求,客户端会因为抛出ServletException而得到通知。
synchronizeSession 指定controller是否同步用户的HTTP session。
cacheSeconds 指定controller通知客户端对数据内容缓存的秒数,一般为大于零的整数。默认值为-1,即不缓存。
useExpiresHeader 指定Controller在响应请求时是否兼容HTTP 1.0 Expires header。缺省值为true
useCacheHeader 指定Controller在相应请求时是否兼容HTTP 1.1 Cache-Control header。默认值为true

当从AbstractController继承时,需要实现handleRequestInternal(HttpServletRequest, HttpServletResponse)抽象方法,该方法将用来实现自己的逻辑,并返回一个ModelAndView对象。下面这个简单的例子演示了如何从AbstractController继承以及如何在applicationContext.xml中进行配置

package samples;

public class SampleController extends AbstractController {

    public ModelAndView handleRequestInternal(
        HttpServletRequest request,
        HttpServletResponse response) throws Exception {

        ModelAndView mav = new ModelAndView("hello");
        mav.addObject("message", "Hello World!");
        return mav;        
    }
}
<bean id="sampleController" class="samples.SampleController">
    <property name="cacheSeconds" value="120"/>
</bean>

该controller返回的ModelAndView使用了硬编码的视图名(尽管这样做不好),并通知客户端将响应数据缓存2分钟。除了通过以上方式创建和配置controller之外,还需要配置handler mapping(请参考第 13.4 节 “处理器映射(handler mapping)”),这样该controller就可以工作了。

13.3.2. 其它的简单控制器

尽管可以继承AbstractController来实现自己的控制器,不过Spring提供的众多控制器减轻了我们开发简单MVC应用时的负担。ParameterizableViewController基本上和上面例子中的一样,不同的是,你可以在applicationContext.xml配置中指定返回视图名从而避免了在Java代码中的硬编码。

UrlFilenameViewController会检查URL,获取文件请求的文件名,并把它作为视图名加以使用。。例如,http://www.springframework.org/index.html对应的视图文件名是index

13.3.3. MultiActionController

MultiActionController将多个行为(action)合并在一个控制器里,这样可以把相关功能组合在一起。MultiActionController位于org.springframework.web.mvc.multiaction包中,它通过将请求映射到正确的方法名来调用方法。当在一个控制器存在大量公共的行为,但是有多个调用入口时,使用MultiActionController就特别方便。

表 13.4. MultiActionController提供的功能

功能 描述
delegate MultiActionController有两种使用方式。第一种是你继承MultiActionController,并在子类中指定由MethodNameResolver解析的方法(这种情况下不需要这个delegate参数)。第二种是你定义一个代理对象,由它提供MethodNameResolver解析出来的方法(这种情况下,你必须使用这个配置参数定义代理对象)。
methodNameResolver MultiActionController需要一种策略,使其可以通过解析请求信息来获得要调用的方法。这个解析策略由MethodNameResolver这个接口定义的。这个参数允许你实现MethodNameResolver接口,然后在控制器中使用你的策略。

MultiActionController所支持的方法需要符合下列格式:

// anyMeaningfulName can be replaced by any methodname
public [ModelAndView | Map | void] anyMeaningfulName(HttpServletRequest, HttpServletResponse [, Exception | AnyObject]);

注意:在此不允许方法重载,因为MultiActionController无法分辨出重载(overloading)了的方法。此外,你可以定义exception handler来处理方法中抛出的异常。

Exception 参数是可选的,它可以是任何异常,只要它是java.lang.Exceptionjava.lang.RuntimeException的子类。AnyObject参数也是可选的,它可以是任何对象。HTTP Request中的参数会存在这个对象中,以便使用。

下面几个例子示范了MultiActionController正确的方法定义。

标准格式(跟Controller接口定义的一样)。

public ModelAndView doRequest(HttpServletRequest, HttpServletResponse)

下面这个方法支持Login参数, 这个参数中包含从请求中抽取出来的信息。

public ModelAndView doLogin(HttpServletRequest, HttpServletResponse, Login)

下面这个方法可以处理Exception

public ModelAndView processException(HttpServletRequest, HttpServletResponse, IllegalArgumentException)

下面这个方法不返回任何数值。 (请参考后面的章节 第 13.11 节 “惯例优先原则(convention over configuration)”)

public void goHome(HttpServletRequest, HttpServletResponse)

This signature has a Map return type (see the section entitled 第 13.11 节 “惯例优先原则(convention over configuration)” below).

下面这个方法返回一个Map。 (请参考后面的章节第 13.11 节 “惯例优先原则(convention over configuration)”)

public Map doRequest(HttpServletRequest, HttpServletResponse)

MethodNameResolver负责从请求中解析出需要调用的方法名称。下面是Spring中内置的三个MethodNameResolver 实现。

  • ParameterMethodNameResolver - 解析请求参数,并将它作为方法名。(对应http://www.sf.net/index.view?testParam=testIt的请求,会调用 testIt(HttpServletRequest,HttpServletResponse)方法)。使用paramName配置参数,可以设定要检查的参数。

  • InternalPathMethodNameResolver -从路径中获取文件名作为方法名 (http://www.sf.net/testing.view的请求会调用testing(HttpServletRequest,HttpServletResponse)方法。

  • PropertiesMethodNameResolver - 使用用户自定义的属性对象,将请求的URL映射到方法名。当属性中包含/index/welcome.html=doIt时,发到/index/welcome.html 的请求会调用doIt(HttpServletRequest, HttpServletResponse)这个方法。 这个方法名解析器可以和PathMatcher一起工作,比如上边那个URL写成/**/welcom?.html也是可以的。

我们来看一组例子。首先是一个使用ParameterMethodNameResolver和代理(delegate)属性的例子,它接受包含参数名"method"的请求,调用方法retrieveIndex

<bean id="paramResolver" class="org....mvc.multiaction.ParameterMethodNameResolver">
  <property name="paramName" value="method"/>
</bean>

<bean id="paramMultiController" class="org....mvc.multiaction.MultiActionController">
  <property name="methodNameResolver" ref="paramResolver"/>
  <property name="delegate" ref="sampleDelegate"/>
</bean>

<bean id="sampleDelegate" class="samples.SampleDelegate"/>

## together with

public class SampleDelegate {

    public ModelAndView retrieveIndex(HttpServletRequest req, HttpServletResponse resp) {

        return new ModelAndView("index", "date", new Long(System.currentTimeMillis()));
    }
}

当使用上面的代理对象时,我们也可以使用PropertiesMethodNameRseolver来匹配一组URL,将它们映射到我们定义的方法上:

<bean id="propsResolver" class="org....mvc.multiaction.PropertiesMethodNameResolver">
  <property name="mappings">
    <value>
        /index/welcome.html=retrieveIndex
        /**/notwelcome.html=retrieveIndex
        /*/user?.html=retrieveIndex
    </value>
  </property>
</bean>

<bean id="paramMultiController" class="org....mvc.multiaction.MultiActionController">
    <property name="methodNameResolver" ref="propsResolver"/>
    <property name="delegate" ref="sampleDelegate"/>
</bean>

13.3.4. 命令控制器

Spring的CommandController是Spring MVC的重要部分。命令控制器提供了一种和数据对象交互的方式,并动态地将来自HttpServletRequest的参数绑定到你指定的数据对象上。它的功能和Struts中的ActionForm有点像,不过在Spring中,你不需要实现任何接口来实现数据绑定。首先,让我们看一下有哪些可以使用的命令控制器:

  • AbstractCommandController --你可以使用该抽象命令控制器来创建自己的命令控制器,它能够将请求参数绑定到你指定的命令对象。这个类并不提供任何表单功能,但是它提供验证功能,并且让你在子类中去实现如何处理由请求参数产生的命令对象。

  • AbstractFormController--一个支持表单提交的抽象控制器类。使用这个控制器,你可以定义表单,并使用从控制器获取的数据对象构建表单。当用户输入表单内容,AbstractFormController将用户输入的内容绑定到命令对象,验证表单内容,并将该对象交给控制器,完成相应的操作。它支持的功能有防止重复提交、表单验证以及一般的表单处理流程。子类需要实现自己的方法来指定采用哪个视图来显示输入表单,哪个视图显示表单正确提交后的结果。如果你需要表单,但不想在应用上下文中指定显示给用户的视图,就使用这个控制器。

  • SimpleFormController --这是一个form cotnroller,当需要根据命令对象来创建相应的form的时候,该类可以提供更多的支持。你可以为其指定一个命令对象,显示表单的视图名,当表单提交成功后显示给用户的视图名等等。

  • AbstractWizardFormController --这是一个抽象类,继承这个类需要实现validatePage()processFinish()processCancel() 方法。

    你有可能也需要写一个构造器,它至少需要调用setPages()setCommandName()方法。setPages()的参数是一个String数组,这个数组包含了组成向导的视图名。setCommandName()的参数是一个String,该参数将用来在视图中调用你的命令对象。

    AbstractFormController的实现一样, 你需要使用命令对象(其实就是一个JavaBean, 这个bean中包含了表单的信息)。你有两个选择:在构造函数中调用setCommandClass()方法(参数是命令对象的类名),或者实现formBackingObject()方法。

    AbstractWizardFormController 有几个你可以复写(override)的方法。最有用的一个是referenceData(..)。这个方法允许你把模型数据以Map的格式传递给视图;getTargetPage() 允许你动态地更改向导的页面顺序,或者直接跳过某些页面;onBindAndValidate() 允许你复写内置的绑定和验证流程。

    最后,我们有必要提一下setAllowDirtyBack()setAllowDirtyForward()两个方法。 你可以在getTargetPage()中调用这两个方法,这两个方法将决定在当前页面验证失败时,是否允许向导前移或后退。

    AbstractWizardFormController的更详细内容请参考JavaDoc。在Spring附带的例子jPetStore中,有一个关于向导实现的例子: org.springframework.samples.jpetstore.web.spring.OrderFormController