14.3.控制器
14.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接口仅仅定义了每个控制器都必须提供的基本功能:处理请求并返回一个模型和一个视图。
14.3.1. AbstractController
和 WebContentGenerator
为了提供一套基础设施,所有的Spring控制器都继承了 AbstractController
,AbstractController
提供了诸如缓存支持和mimetype设置这样的功能。
表 14.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"> <property name="cacheSeconds" value="120"/> </bean>
该controller返回的ModelAndView使用了硬编码的视图名(尽管这样做不好),并通知客户端将响应数据缓存2分钟。除了通过以上方式创建和配置controller之外,还需要配置handler mapping(请参考第 14.4 节 “处理器映射(handler mapping)”),这样该controller就可以工作了。
14.3.2. 其它的简单控制器
尽管可以继承AbstractController
来实现自己的控制器,不过Spring提供的众多控制器减轻了我们开发简单MVC应用时的负担。ParameterizableViewController
基本上和上面例子中的一样,不同的是,你可以在applicationContext.xml配置中指定返回视图名从而避免了在Java代码中的硬编码。
UrlFilenameViewController
会检查URL,获取文件请求的文件名,并把它作为视图名加以使用。。例如,http://www.springframework.org/index.html
对应的视图文件名是index
。
14.3.3. MultiActionController
MultiActionController
将多个行为(action)合并在一个控制器里,这样可以把相关功能组合在一起。MultiActionController
位于org.springframework.web.mvc.multiaction
包中,它通过将请求映射到正确的方法名来调用方法。当在一个控制器存在大量公共的行为,但是有多个调用入口时,使用MultiActionController
就特别方便。
表 14.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.Exception
或java.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)
下面这个方法不返回任何数值。 (请参考后面的章节 第 14.11 节 “惯例优先原则(convention over configuration)”)
public void goHome(HttpServletRequest, HttpServletResponse)
This signature has a Map
return type (see the section entitled 第 14.11 节 “惯例优先原则(convention over configuration)” below).
下面这个方法返回一个Map
。 (请参考后面的章节第 14.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"> <property name="paramName" value="method"/> </bean> <bean id="paramMultiController"> <property name="methodNameResolver" ref="paramResolver"/> <property name="delegate" ref="sampleDelegate"/> </bean> <bean id="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"> <property name="mappings"> <value> /index/welcome.html=retrieveIndex /**/notwelcome.html=retrieveIndex /*/user?.html=retrieveIndex </value> </property> </bean> <bean id="paramMultiController"> <property name="methodNameResolver" ref="propsResolver"/> <property name="delegate" ref="sampleDelegate"/> </bean>
14.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
。