?
This document uses PHP Chinese website manual Release
控制器的概念是MVC設(shè)計(jì)模式的一部分(確切地說(shuō),是MVC中的C)。應(yīng)用程序的行為通常被定義為服務(wù)接口, 而控制器使得用戶可以訪問(wèn)應(yīng)用所提供的服務(wù)??刂破鹘馕鲇脩糨斎?,并將其轉(zhuǎn)換成合理的模型數(shù)據(jù),從而可以進(jìn)一步由視圖展示給用戶。 Spring以一種抽象的方式實(shí)現(xiàn)了控制器概念,這樣可以支持不同類型的控制器。Spring本身包含表單控制器、命令控制器、向?qū)涂刂破鞯榷喾N多樣的控制器。
Spring控制器架構(gòu)的基礎(chǔ)是org.springframework.mvc.Controller
接口,其代碼如下:
public interface Controller { ModelAndView handleRequest( HttpServletRequest request, HttpServletResponse response) throws Exception; }
可以發(fā)現(xiàn)Controller
接口僅僅聲明了一個(gè)方法,它負(fù)責(zé)處理請(qǐng)求并返回合適的模型和視圖。Spring MVC實(shí)現(xiàn)的基礎(chǔ)
就是這三個(gè)概念:Mdel、View(ModelAndView
)以及 Controller
。雖然
Controller
接口是完全抽象的,但Spring也提供了許多你可能會(huì)用到的控制器。Controller接口僅僅定義了每個(gè)控制器都必須提供的基本功能:
處理請(qǐng)求并返回一個(gè)模型和一個(gè)視圖。
為提供一套基礎(chǔ)設(shè)施,所有的Spring控制器都繼承了 AbstractController
,AbstractController
提供了諸如緩存支持和mimetype設(shè)置這樣的功能。
表?13.3.?AbstractController
提供的功能
功能 | 描述 |
---|---|
supportedMethods |
指定這個(gè)控制器應(yīng)該接受什么樣的請(qǐng)求方法。通常它被設(shè)置成同時(shí)支持GET和POST,但是可以選擇你想支持的方法。如果控制器不支持請(qǐng)求發(fā)送的方法,
客戶端會(huì)得到通知(通常是拋出一個(gè)ServletException )。
|
requiresSession |
表明這個(gè)控制器是否需要HTTP session才能正常工作。如果控制器在沒(méi)有session的情況下接收到請(qǐng)求,客戶端會(huì)因?yàn)閽伋?code class="classname">ServletException 而得到通知。 |
synchronizeOnSession |
指定controller是否同步用戶的HTTP session。 |
cacheSeconds |
指定controller通知客戶端對(duì)數(shù)據(jù)內(nèi)容緩存的秒數(shù),一般為大于零的整數(shù)。默認(rèn)值為-1,即不緩存。 |
useExpiresHeader |
指定Controller在響應(yīng)請(qǐng)求時(shí)是否兼容HTTP 1.0 Expires header。缺省值為true 。 |
useCacheHeader |
指定Controller在相應(yīng)請(qǐng)求時(shí)是否兼容HTTP 1.1 Cache-Control header。默認(rèn)值為true 。 |
當(dāng)從AbstractController
繼承時(shí),只需要實(shí)現(xiàn)handleRequestInternal(HttpServletRequest,
HttpServletResponse)
抽象方法,該方法將用來(lái)實(shí)現(xiàn)自定義的邏輯,并返回一個(gè)ModelAndView
對(duì)象。下面這個(gè)簡(jiǎn)單的例子演示
了如何從AbstractController
繼承以及如何在applicationContext.xml中進(jìn)行配置。
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>
要讓該簡(jiǎn)單控制器工作, 除創(chuàng)建一個(gè)handler mapping(請(qǐng)參考第?13.4?節(jié) “處理器映射(handler mapping)”一節(jié))外, 需要的全部就是上面的類和在web application context中的聲明。 該controller在再次檢查前,通知客戶端將響應(yīng)數(shù)據(jù)緩存2分鐘,并返回使用硬編碼的視圖名(盡管這樣做不好)。
盡管可以繼承AbstractController
來(lái)實(shí)現(xiàn)自己的控制器,不過(guò)Spring提供的眾多控制器減輕了我們開(kāi)發(fā)簡(jiǎn)單MVC應(yīng)用時(shí)的負(fù)擔(dān)。
ParameterizableViewController
基本上和上面例子中的一樣,不同的是,可以在application context中指定返回的視圖名稱(從而
避免了在Java代碼中的硬編碼)。
UrlFilenameViewController
會(huì)檢查URL,獲取文件請(qǐng)求的文件名,并把它作為視圖名加以使用。。例如,
http://www.springframework.org/index.html
對(duì)應(yīng)的視圖文件名是index
。
Spring提供了MultiActionController
來(lái)將多個(gè)請(qǐng)求處理方法合并在一個(gè)控制器里,這樣可以把相關(guān)功能組合在一起。
(如果你很熟悉Struts,會(huì)發(fā)現(xiàn)這與Struts的
位于DispatchAction
很像)
MultiActionControllerorg.springframework.web.mvc.multiaction
包中,它可以定義頁(yè)面請(qǐng)求到控制器方法名的映射,
然后在處理相應(yīng)請(qǐng)求時(shí)調(diào)用該方法。當(dāng)你有很多比較小的且相關(guān)的功能時(shí)使用MultiActionController
很方便,這樣就不必為每個(gè)小功能創(chuàng)建
一個(gè)單獨(dú)的Controller
了。但是一般來(lái)說(shuō)MultiActionController
不適合處理復(fù)雜邏輯,或者完全不相關(guān)
的功能,這時(shí)應(yīng)該堅(jiān)持使用標(biāo)準(zhǔn)方法,當(dāng)在一個(gè)控制器存在大量公共的行為,但是有多個(gè)調(diào)用入口時(shí),使用MultiActionController
就特別方便。
MultiActionController
有兩種使用方式:一是創(chuàng)建MultiActionController
的子類,并指定將被
MethodNameResolver
解析的方法(這種情況下不需要這個(gè)delegate參數(shù));二是定義一個(gè)委托對(duì)象,
MethodNameResolver
解析出目標(biāo)方法后將調(diào)用該對(duì)象的相應(yīng)方法。這種情況下需要定義MultiActionController
的實(shí)例并將委托對(duì)象作為協(xié)作者注入(可通過(guò)構(gòu)造參數(shù)或者setDelegate
方法)。
MultiActionController
需要一種策略,使其可以通過(guò)解析請(qǐng)求信息來(lái)獲得要調(diào)用的方法。這個(gè)解析策略由
MethodNameResolver
接口定義。MultiActionController
提供了'methodNameResolver
'
屬性使得你可以注入需要的MethodNameResolver
。在自己的MultiActionController
(或者前面說(shuō)的委托對(duì)象)
上定義的請(qǐng)求處理方法必須符合如下簽名:
// 'anyMeaningfulName
'指任意方法名
public [ModelAndView | Map | void] anyMeaningfulName(HttpServletRequest, HttpServletResponse [,HttpSession] [,AnyObject])
上述方法的詳細(xì)信息可參考
MultiActionController類 Javadoc。如果打算使用MultiActionController
,那最好看看它的Javadoc。不過(guò),下面提供了
一些關(guān)于合法的請(qǐng)求處理方法的基本例子。
標(biāo)準(zhǔn)格式(跟Controller
接口定義的一樣)。
public ModelAndView displayCatalog(HttpServletRequest, HttpServletResponse)
下面這個(gè)方法接收Login
參數(shù),該參數(shù)中包含從請(qǐng)求中抽取出來(lái)的信息。
public ModelAndView login(HttpServletRequest, HttpServletResponse, Login)
下面這個(gè)方法要求請(qǐng)求中已經(jīng)存在合法的session對(duì)象。
public ModelAndView viewCart(HttpServletRequest, HttpServletResponse, HttpSession)
下面這個(gè)方法接受一個(gè)Product
參數(shù),這個(gè)參數(shù)包含從請(qǐng)求中抽取出來(lái)的信息,并且要求請(qǐng)求中已經(jīng)存在一個(gè)
合法的session對(duì)象。注意參數(shù)的順序很重要:session必須是第三個(gè)參數(shù),而綁定參數(shù)必須是final的,并位于session之后。
public ModelAndView updateCart(HttpServletRequest, HttpServletResponse, HttpSession, Product)
下面這個(gè)方法聲明返回void
類型,這說(shuō)明它會(huì)直接寫response。
public void home(HttpServletRequest, HttpServletResponse)
下面這個(gè)方法返回Map
,表明視圖解析器應(yīng)該從請(qǐng)求中抽取視圖名,而返回?cái)?shù)據(jù)將被放入model
(參考第?13.11?節(jié) “慣例優(yōu)先原則(convention over configuration)”)。
public Map list(HttpServletRequest, HttpServletResponse)
MethodNameResolver
負(fù)責(zé)從請(qǐng)求中解析出需要調(diào)用的方法名稱。Spring本身已經(jīng)提供了一系列
MethodNameResolver
的實(shí)現(xiàn),當(dāng)然也可以編寫自己的實(shí)現(xiàn)。注意,如果沒(méi)有明確注入自己的實(shí)現(xiàn),Spring默認(rèn)使用
InternalPathMethodNameResolver
。
InternalPathMethodNameResolver
-從請(qǐng)求路徑中獲取文件名作為方法名
比如,http://www.sf.net/testing.view
的請(qǐng)求會(huì)調(diào)用testing(HttpServletRequest,HttpServletResponse)
方法。
ParameterMethodNameResolver
- 解析請(qǐng)求參數(shù),并將它作為方法名。
比如,對(duì)應(yīng)http://www.sf.net/index.view?method=testIt
的請(qǐng)求,會(huì)調(diào)用
testIt(HttpServletRequest, HttpServletResponse)
方法)。使用paramName
屬性定義要使用的請(qǐng)求參數(shù)名稱。
PropertiesMethodNameResolver
- 使用用戶自定義的屬性(Properties)對(duì)象,將請(qǐng)求的URL映射到方法名。比如,當(dāng)屬性中包含
/index/welcome.html=doIt
時(shí),對(duì)/index/welcome.html
的請(qǐng)求會(huì)調(diào)用
doIt(HttpServletRequest, HttpServletResponse)
方法。 PropertiesMethodNameResolver
內(nèi)部使用了
Spring的PathMatcher
,所以支持路徑通配符,比如上邊那個(gè)URL寫成welcom?.html
也是可以的。
可以聲明自己的方法來(lái)處理請(qǐng)求處理過(guò)程中產(chǎn)生的Exceptions
。該方法的簽名與請(qǐng)求處理方法的簽名類似:第一個(gè)參數(shù)必須是
HttpServletRequest
,第二個(gè)參數(shù)必須是HttpServletResponse
。不過(guò)與請(qǐng)求處理
方法不同的是,該方法的名字可以任意,具體匹配策略由該方法的第三個(gè)參數(shù)(參數(shù)類型必須是一種Exception
)決定。Spring根據(jù)最接近的
異常類型進(jìn)行匹配。下面是一個(gè)這種異常處理方法簽名的例子:
public ModelAndView processException(HttpServletRequest, HttpServletResponse, IllegalArgumentException)
我們來(lái)看一個(gè)例子,其中展示了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())); } }
當(dāng)使用上述的委托方式時(shí),我們需要配置PropertiesMethodNameResolver
,來(lái)完成與我們定義的方法的任意數(shù)量的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>
這個(gè)處理器映射首先將對(duì)所有目錄中文件名為help.html
的請(qǐng)求傳遞給helpController
。
helpController
是一個(gè)UrlFilenameViewController
(要了解更多關(guān)于控制器的信息,請(qǐng)參閱第?13.3?節(jié) “控制器”)。
對(duì)ex
目錄中所有以view
開(kāi)始,以.html
結(jié)尾的請(qǐng)求都會(huì)被傳遞給helpController
。
同樣的,我們也為editAccountFormController
定義了兩個(gè)映射。
Spring的處理器映射支持?jǐn)r截器。當(dāng)你想要為某些請(qǐng)求提供特殊功能時(shí),例如對(duì)用戶進(jìn)行身份認(rèn)證,這就非常有用。
處理器映射中的攔截器必須實(shí)現(xiàn)org.springframework.web.servlet
包中的HandlerInterceptor
接口。
這個(gè)接口定義了三個(gè)方法,一個(gè)在處理器執(zhí)行前被調(diào)用,一個(gè)在處理器執(zhí)行后被調(diào)用,另一個(gè)在整個(gè)請(qǐng)求處理完后調(diào)用。
這三個(gè)方法提供你足夠的靈活度做任何處理前后的操作。
preHandle(..)
方法有一個(gè)boolean返回值。
使用這個(gè)值,可以調(diào)整執(zhí)行鏈的行為。
當(dāng)返回true
時(shí),處理器執(zhí)行鏈將繼續(xù)執(zhí)行,當(dāng)返回false
時(shí),DispatcherServlet
認(rèn)為該攔截器已經(jīng)處理完了請(qǐng)求(比如顯示正確的視圖),而不繼續(xù)執(zhí)行執(zhí)行鏈中的其它攔截器和處理器。
下面的例子提供了一個(gè)攔截器,它攔截所有請(qǐng)求,如果當(dāng)前時(shí)間不是在上午9點(diǎn)到下午6點(diǎn),它將用戶重定向到某個(gè)頁(yè)面。
<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>
在上面這個(gè)例子中,所有對(duì)*.view
資源的請(qǐng)求,只要包含參數(shù)siteLanguage
,
都會(huì)改變本地化信息。比如下面這個(gè)請(qǐng)求http://www.sf.net/home.view?siteLanguage=nl
會(huì)將網(wǎng)站語(yǔ)言修改為荷蘭語(yǔ)。