©
本文档使用 PHP中文网手册 发布
控制器的概念是MVC设计模式的一部分(确切地说,是MVC中的C)。应用程序的行为通常被定义为服务接口, 而控制器使得用户可以访问应用所提供的服务。控制器解析用户输入,并将其转换成合理的模型数据,从而可以进一步由视图展示给用户。 Spring以一种抽象的方式实现了控制器概念,这样可以支持不同类型的控制器。Spring本身包含表单控制器、命令控制器、向导型控制器等多种多样的控制器。
Spring控制器架构的基础是org.springframework.mvc.Controller
接口,其代码如下:
public interface Controller { 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 account.form=editAccountFormController help.html=helpController </value> </property> </bean> <bean id="helpController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/> <bean id="editAccountFormController" class="org.springframework.web.servlet.mvc.SimpleFormController"> <property name="formView" value="account"/> <property name="successView" value="account-created"/> <property name="commandName" value="Account"/> <property name="commandClass" value="samples.Account"/> </bean> <beans>
这个处理器映射首先将对所有目录中文件名为help.html
的请求传递给helpController
。
helpController
是一个UrlFilenameViewController
(要了解更多关于控制器的信息,请参阅第 13.3 节 “控制器”)。
对ex
目录中所有以view
开始,以.html
结尾的请求都会被传递给helpController
。
同样的,我们也为editAccountFormController
定义了两个映射。
Spring的处理器映射支持拦截器。当你想要为某些请求提供特殊功能时,例如对用户进行身份认证,这就非常有用。
处理器映射中的拦截器必须实现org.springframework.web.servlet
包中的HandlerInterceptor
接口。
这个接口定义了三个方法,一个在处理器执行前被调用,一个在处理器执行后被调用,另一个在整个请求处理完后调用。
这三个方法提供你足够的灵活度做任何处理前后的操作。
preHandle(..)
方法有一个boolean返回值。
使用这个值,可以调整执行链的行为。
当返回true
时,处理器执行链将继续执行,当返回false
时,DispatcherServlet
认为该拦截器已经处理完了请求(比如显示正确的视图),而不继续执行执行链中的其它拦截器和处理器。
下面的例子提供了一个拦截器,它拦截所有请求,如果当前时间不是在上午9点到下午6点,它将用户重定向到某个页面。
<beans> <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="interceptors"> <list> <ref bean="officeHoursInterceptor"/> </list> </property> <property name="mappings"> <value> *.view=someController</value> </property> </bean>
在上面这个例子中,所有对*.view
资源的请求,只要包含参数siteLanguage
,
都会改变本地化信息。比如下面这个请求http://www.sf.net/home.view?siteLanguage=nl
会将网站语言修改为荷兰语。