控制器的概念是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
很像)
MultiActionController位于org.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
。