控制器的概念是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接口仅仅定义了每个控制器都必须提供的基本功能:
处理请求并返回一个模型和一个视图。
为提供一套基础设施,所有的Spring控制器都继承了 AbstractController ,AbstractController
提供了诸如缓存支持和mimetype设置这样的功能。
表 13.3. AbstractController提供的功能
| 功能 | 描述 |
|---|---|
supportedMethods |
指定这个控制器应该接受什么样的请求方法。通常它被设置成同时支持GET和POST,但是可以选择你想支持的方法。如果控制器不支持请求发送的方法,
客户端会得到通知(通常是抛出一个ServletException)。
|
requiresSession |
表明这个控制器是否需要HTTP session才能正常工作。如果控制器在没有session的情况下接收到请求,客户端会因为抛出ServletException
而得到通知。
|
synchronizeOnSession |
指定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>
要让该简单控制器工作, 除创建一个handler mapping(请参考第 13.4 节 “处理器映射(handler mapping)”一节)外, 需要的全部就是上面的类和在web application context中的声明。 该controller在再次检查前,通知客户端将响应数据缓存2分钟,并返回使用硬编码的视图名(尽管这样做不好)。
尽管可以继承AbstractController来实现自己的控制器,不过Spring提供的众多控制器减轻了我们开发简单MVC应用时的负担。
ParameterizableViewController基本上和上面例子中的一样,不同的是,可以在application context中指定返回的视图名称(从而
避免了在Java代码中的硬编码)。
UrlFilenameViewController会检查URL,获取文件请求的文件名,并把它作为视图名加以使用。。例如,
http://www.springframework.org/index.html对应的视图文件名是index。
Spring提供了MultiActionController来将多个请求处理方法合并在一个控制器里,这样可以把相关功能组合在一起。
(如果你很熟悉Struts,会发现这与Struts的位于DispatchAction很像)
MultiActionControllerorg.springframework.web.mvc.multiaction包中,它可以定义页面请求到控制器方法名的映射,
然后在处理相应请求时调用该方法。当你有很多比较小的且相关的功能时使用MultiActionController很方便,这样就不必为每个小功能创建
一个单独的Controller了。但是一般来说MultiActionController不适合处理复杂逻辑,或者完全不相关
的功能,这时应该坚持使用标准方法,当在一个控制器存在大量公共的行为,但是有多个调用入口时,使用MultiActionController就特别方便。
MultiActionController有两种使用方式:一是创建MultiActionController的子类,并指定将被
MethodNameResolver解析的方法(这种情况下不需要这个delegate参数);二是定义一个委托对象,
MethodNameResolver解析出目标方法后将调用该对象的相应方法。这种情况下需要定义MultiActionController
的实例并将委托对象作为协作者注入(可通过构造参数或者setDelegate方法)。
MultiActionController需要一种策略,使其可以通过解析请求信息来获得要调用的方法。这个解析策略由
MethodNameResolver接口定义。MultiActionController提供了'methodNameResolver'
属性使得你可以注入需要的MethodNameResolver。在自己的MultiActionController(或者前面说的委托对象)
上定义的请求处理方法必须符合如下签名:
// 'anyMeaningfulName'指任意方法名
public [ModelAndView | Map | void] anyMeaningfulName(HttpServletRequest, HttpServletResponse [,HttpSession] [,AnyObject])
上述方法的详细信息可参考
MultiActionController类 Javadoc。如果打算使用MultiActionController,那最好看看它的Javadoc。不过,下面提供了
一些关于合法的请求处理方法的基本例子。
标准格式(跟Controller接口定义的一样)。
public ModelAndView displayCatalog(HttpServletRequest, HttpServletResponse)
下面这个方法接收Login参数,该参数中包含从请求中抽取出来的信息。
public ModelAndView login(HttpServletRequest, HttpServletResponse, Login)
下面这个方法要求请求中已经存在合法的session对象。
public ModelAndView viewCart(HttpServletRequest, HttpServletResponse, HttpSession)
下面这个方法接受一个Product参数,这个参数包含从请求中抽取出来的信息,并且要求请求中已经存在一个
合法的session对象。注意参数的顺序很重要:session必须是第三个参数,而绑定参数必须是final的,并位于session之后。
public ModelAndView updateCart(HttpServletRequest, HttpServletResponse, HttpSession, Product)
下面这个方法声明返回void类型,这说明它会直接写response。
public void home(HttpServletRequest, HttpServletResponse)
下面这个方法返回Map,表明视图解析器应该从请求中抽取视图名,而返回数据将被放入model
(参考第 13.11 节 “惯例优先原则(convention over configuration)”)。
public Map list(HttpServletRequest, HttpServletResponse)
MethodNameResolver负责从请求中解析出需要调用的方法名称。Spring本身已经提供了一系列
MethodNameResolver的实现,当然也可以编写自己的实现。注意,如果没有明确注入自己的实现,Spring默认使用
InternalPathMethodNameResolver。
InternalPathMethodNameResolver -从请求路径中获取文件名作为方法名
比如,http://www.sf.net/testing.view的请求会调用testing(HttpServletRequest,HttpServletResponse)方法。
ParameterMethodNameResolver - 解析请求参数,并将它作为方法名。
比如,对应http://www.sf.net/index.view?method=testIt的请求,会调用
testIt(HttpServletRequest, HttpServletResponse)方法)。使用paramName属性定义要使用的请求参数名称。
PropertiesMethodNameResolver - 使用用户自定义的属性(Properties)对象,将请求的URL映射到方法名。比如,当属性中包含
/index/welcome.html=doIt时,对/index/welcome.html 的请求会调用
doIt(HttpServletRequest, HttpServletResponse)方法。 PropertiesMethodNameResolver内部使用了
Spring的PathMatcher,所以支持路径通配符,比如上边那个URL写成/**/welcom?.html也是可以的。
可以声明自己的方法来处理请求处理过程中产生的Exceptions。该方法的签名与请求处理方法的签名类似:第一个参数必须是
HttpServletRequest,第二个参数必须是HttpServletResponse。不过与请求处理
方法不同的是,该方法的名字可以任意,具体匹配策略由该方法的第三个参数(参数类型必须是一种Exception)决定。Spring根据最接近的
异常类型进行匹配。下面是一个这种异常处理方法签名的例子:
public ModelAndView processException(HttpServletRequest, HttpServletResponse, IllegalArgumentException)
我们来看一个例子,其中展示了MultiActionController与ParameterMethodNameResolver一同使用的委托方式。
<bean id="paramMultiController"
class="org.springframework.web.servlet.mvc.multiaction.MultiActionController">
<property name="methodNameResolver">
<bean class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">
<property name="paramName" value="method"/>
</bean>
</property>
<property name="delegate">
<bean class="samples.SampleDelegate"/>
</property>
</bean>
}
public class SampleDelegate {
public ModelAndView retrieveIndex(HttpServletRequest req, HttpServletResponse resp) {
return new ModelAndView("index", "date", new Long(System.currentTimeMillis()));
}
}
当使用上述的委托方式时,我们需要配置PropertiesMethodNameResolver,来完成与我们定义的方法的任意数量的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">
<bean class="samples.SampleDelegate"/>
</property>
</bean>
Spring的command controllers是Spring MVC的重要部分。
命令控制器提供了一种和数据对象交互的方式,并动态地将来自HttpServletRequest的参数绑定到指定的数据对象上。
它的功能和Struts中的ActionForm有点像,不过在Spring中,不需要实现任何接口来实现数据绑定。
首先,让我们看一下有哪些可以使用的命令控制器:
AbstractCommandController - 可以使用该抽象命令控制器来创建自己的命令控制器,它能够将请求参数绑定到指定的命令对象。
这个类并不提供任何表单功能,但是它提供验证功能,并且让你在控制器中去实现如何处理由请求参数值产生的命令对象。
AbstractFormController - 一个支持表单提交的抽象控制器类。
使用这个控制器,可以定义表单,并使用从控制器获取的数据对象构建表单。
当用户输入表单内容,AbstractFormController将用户输入的内容绑定到命令对象,验证表单内容,
并将该对象交给控制器,完成相应的操作。它支持的功能有防止重复提交、表单验证以及一般的表单处理流程。
子类需要实现自己的方法来指定采用哪个视图来显示输入表单,哪个视图显示表单正确提交后的结果。
如果需要表单,但不想在应用上下文中指定显示给用户的视图,可使用该控制器。
SimpleFormController - 这是一个form controller,当需要根据命令对象来创建相应的form的时候,该类可以提供更多的支持。
可以为其指定一个命令对象,显示表单的视图名,当表单提交成功后显示给用户的视图名等等。
AbstractWizardFormController - 这是一个抽象类,继承这个类需要实现validatePage()、processFinish() 和processCancel() 方法。
你有可能也需要写一个contractor,它至少需要调用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。