當(dāng)前位置:
首頁 >
前端技术
> javascript
>内容正文
javascript
Spring 2.5:Spring MVC中的新特性
生活随笔
收集整理的這篇文章主要介紹了
Spring 2.5:Spring MVC中的新特性
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
轉(zhuǎn)載說明:infoQ就是牛人多,看人家去年就把Spring2.5注視驅(qū)動(dòng)的MVC寫出來了,還是這么詳細(xì),我真是自嘆不如,今天偶爾看到這篇文章非常認(rèn)真的拜讀了2遍,簡直是茅廁頓開啊....\(^o^)/~<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> PS:如果各位看客覺得這篇文章看不太懂,千萬別著急,我自己曾經(jīng)寫了兩篇比較基礎(chǔ)的,您可以先看看我寫的那兩篇之后再看這篇,就會(huì)有很多收獲.. 廣告時(shí)間:~\(≧▽≦)/~ Spring2.5注釋驅(qū)動(dòng)與基于注釋的MVC Spring2.5注釋語法(上)——Spring2.5注釋驅(qū)動(dòng)的IoC ? 本文轉(zhuǎn)載自Spring 2.5:Spring MVC中的新特性 ? Spring框架從創(chuàng)建伊始就致力于為復(fù)雜問題提供強(qiáng)大的、非侵入性的解決方案。Spring 2.0當(dāng)中為縮減XML配置文件數(shù)量引入定制命名空間功能,從此它便深深植根于核心Spring框架(aop、context、jee、jms、 lang、tx和util命名空間)、Spring Portfolio項(xiàng)目(例如Spring Security)和非Spring項(xiàng)目中(例如CXF)。 Spring 2.5推出了一整套注解,作為基于XML的配置的替換方案。注解可用于Spring管理對(duì)象的自動(dòng)發(fā)現(xiàn)、依賴注入、生命周期方法、Web層配置和單元/集成測試。 探索Spring 2.5中引入的注解技術(shù)系列文章由三部分組成,本文是其中的第二篇,它主要講述了Web層中的注解支持。最后一篇文章將著重介紹可用于集成和測試的其它特性。 這個(gè)系列文章的第一部分論述了Java注解(annotation)是如何代替XML來配置Spring管理對(duì)象和依賴注入的。我們?cè)儆靡粋€(gè)例子回顧一下: @Controller
public class ClinicController {
??? private final Clinic clinic;
??? @Autowired
??? public ClinicController(Clinic clinic) {
?????? this.clinic = clinic;
??? }
??? ... @Controller表明ClinicController是Web層組件,@Autowired請(qǐng)求一個(gè)被依賴注入的Clinic實(shí)例。這個(gè)例子只需要少量的XML語句就能使容器識(shí)別兩個(gè)注解,并限定組件的掃描范圍: <context:component-scan base-package="org.springframework.samples.petclinic"/> 這對(duì)Web層可謂是個(gè)福音,因?yàn)樵谶@層Spring的XML配置文件已日益臃腫,甚至可能還不如層下的配置來得有用。控制器掌握著許多屬性,例如視圖名稱、表單對(duì)象名稱和驗(yàn)證器類型,這些多是關(guān)乎配置的,甚少關(guān)于依賴注入的。通過bean定義繼承,或者避免配置變化不是很頻繁的屬性,也可以有效的管理類似的配置。不過以我的經(jīng)驗(yàn),很多開發(fā)人員都不會(huì)這樣做,結(jié)果就是XML文件總比實(shí)際需要的要龐大。不過 @Controller和@Autowired對(duì)Web層的配置會(huì)產(chǎn)生積極的作用。 在系列文章的第二部分我們將繼續(xù)討論這個(gè)問題,并瀏覽Spring 2.5在Web層的注解技術(shù)。這些注解被非正式的稱為@MVC,它涉及到了Spring MVC和Spring Porlet MVC,實(shí)際上本文討論的大部分功能都可以應(yīng)用在這兩個(gè)框架上。 從Controller到@Controller 與第一部分討論的注解相比,@MVC已不只是作為配置的一種替換方案這樣簡單了,考慮下面這個(gè)著名的Spring MVC控制器簽名: public interface Controller {
??? ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse
response) throws Exception;
} 所有的Spring MVC控制器要么直接實(shí)現(xiàn)Controller接口,要么就得擴(kuò)展類似AbstractController、 SimpleFormController、 MultiActionController或AbstractWizardFormController這樣的基類實(shí)現(xiàn)。正是Controller接口允許Spring MVC的DispatcherServlet把所有上述對(duì)象都看作是“處理器(handlers)”,并在一個(gè)名為 SimpleControllerHandlerAdapter的適配器的幫助下調(diào)用它們。 @MVC從三個(gè)重要的方面改變了這個(gè)程序設(shè)計(jì)模型:不需要任何接口或者基類。 允許有任意數(shù)量的請(qǐng)求處理方法。 在方法簽名上具有高度的靈活性。 考慮到以上三個(gè)要點(diǎn),就可以說很公平的說@MVC不僅僅是個(gè)替換方案了,它將會(huì)是Spring MVC的控制器技術(shù)演變過程中下一個(gè)重要步驟。 DispatcherServlet在名為AnnotationMethodHandlerAdapter的適配器幫助下調(diào)用被注解的控制器。正是這個(gè)適配器做了大量工作支持我們此后將會(huì)討論的注解,同時(shí)也是它有效的取代了對(duì)于控制器基類的需求。 @RequestMapping簡介 我們還是從一個(gè)類似于傳統(tǒng)的Spring MVC Controller控制器開始: @Controller
public class AccountsController {
??? private AccountRepository accountRepository;
??? @Autowired
??? public AccountsController(AccountRepository accountRepository) {
?????? this.accountRepository = accountRepository;
??? }
??? @RequestMapping("/accounts/show")
??? public ModelAndView show(HttpServletRequest request,
???????????????????????????? HttpServletResponse response) throws Exception {
?????? String number = ServletRequestUtils.getStringParameter(request, "number");
?????? ModelAndView mav = new ModelAndView("/WEB-INF/views/accounts/show.jsp");
?????? mav.addObject("account", accountRepository.findAccount(number));
?????? return mav;
??? }
} 此處與以往的不同在于,這個(gè)控制器并沒有擴(kuò)展Controller接口,并且它用@RequestMapping注解指明show()是映射到URI路徑 “/accounts/show”的請(qǐng)求處理方法。除此以外,其余代碼都是一個(gè)典型的Spring MVC控制器應(yīng)有的內(nèi)容。 在將上述的方法完全轉(zhuǎn)化到@MVC后,我們會(huì)再回過頭來看@RequestMapping,但是在此之前還有一點(diǎn)需要提請(qǐng)注意,上面的請(qǐng)求映射URI也可匹配帶有任意擴(kuò)展名的URI路徑,例如: /accounts/show.htm
/accounts/show.xls
/accounts/show.pdf
... 靈活的請(qǐng)求處理方法簽名 我們?cè)?jīng)承諾過要提供靈活的方法簽名,現(xiàn)在來看一下成果。輸入的參數(shù)中移除了響應(yīng)對(duì)象,增加了一個(gè)代表模型的Map;返回的不再是ModelAndView,而是一個(gè)字符串,指明呈現(xiàn)響應(yīng)時(shí)要用的視圖名字: @RequestMapping("/accounts/show")
public String show(HttpServletRequest request, Map<String, Object> model)
throws Exception {
??? String number = ServletRequestUtils.getStringParameter(request, "number");
??? model.put("account", accountRepository.findAccount(number));
??? return "/WEB-INF/views/accounts/show.jsp";
} Map輸入?yún)?shù)是一個(gè)“隱式的”模型,對(duì)于我們來說在調(diào)用方法前創(chuàng)建它很方便,其中添加的鍵—值對(duì)數(shù)據(jù)便于在視圖中解析應(yīng)用。本例視圖為show.jsp頁面。 @MVC可以接受多種類型的輸入?yún)?shù),例如 HttpServletRequest/HttpServletResponse、HttpSession、Locale、InputStream、 OutputStream、File[]等等,它們的順序不受任何限制;同樣它也允許多種返回類型,例如ModelAndView、Map、 String,或者什么都不返回。你可以查看@RequestMapping的JavaDoc以了解它支持的所有輸入和返回參數(shù)類型。 有種令人感興趣的情形是當(dāng)方法沒有指定視圖時(shí)(例如返回類型為void)會(huì)有什么事情發(fā)生,按照慣例DispatcherServlet要再使用請(qǐng)求URI的路徑信息,不過要移去前面的斜杠和擴(kuò)展名。讓我們把返回類型改為void: @RequestMapping("/accounts/show")
public void show(HttpServletRequest request, Map<String, Object> model) throws Exception {
??? String number = ServletRequestUtils.getStringParameter(request, "number");
??? model.put("account", accountRepository.findAccount(number));
} 對(duì)于給定的請(qǐng)求處理方法和“/accounts/show”的請(qǐng)求映射,我們可以期望DispatcherServlet能夠獲得“accounts/show”的默認(rèn)視圖名稱,當(dāng)它與如下適當(dāng)?shù)囊晥D解析器結(jié)合共同作用時(shí),會(huì)產(chǎn)生與前面指明返回視圖名同樣的結(jié)果: <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
??? <property name="prefix" value="/WEB-INF/views/" />
??? <property name="suffix" value=".jsp" />
</bean> 強(qiáng)烈推薦視圖名稱依賴慣例的方式,因?yàn)檫@樣可以從控制器代碼中消除硬編碼的視圖名稱。如果你想定制 DispatcherServlet獲取默認(rèn)視圖名的方式,就在servlet上下文環(huán)境中配置一個(gè)你自己的 RequestToViewNameTranslator實(shí)現(xiàn),并為其bean id賦名為“viewNameTranslator”。 用@RequestParam提取和解析參數(shù) @MVC另外一個(gè)特性是其提取和解析請(qǐng)求參數(shù)的能力。讓我們繼續(xù)重構(gòu)上面的方法,并在其中添加@RequestParam注解: @RequestMapping("/accounts/show")
public void show(@RequestParam("number") String number, Map<String, Object> model) {
??? model.put("account", accountRepository.findAccount(number));
} 這里@RequestParam注解可以用來提取名為“number”的String類型的參數(shù),并將之作為輸入?yún)?shù)傳入。 @RequestParam支持類型轉(zhuǎn)換,還有必需和可選參數(shù)。類型轉(zhuǎn)換目前支持所有的基本Java類型,你可通過定制的PropertyEditors 來擴(kuò)展它的范圍。下面是一些例子,其中包括了必需和可選參數(shù): @RequestParam(value="number", required=false) String number
@RequestParam("id") Long id
@RequestParam("balance") double balance
@RequestParam double amount 注意,最后一個(gè)例子沒有提供清晰的參數(shù)名。當(dāng)且僅當(dāng)代碼帶調(diào)試符號(hào)編譯時(shí),結(jié)果會(huì)提取名為“amount ”的參數(shù),否則,將拋出IllegalStateException異常,因?yàn)楫?dāng)前的信息不足以從請(qǐng)求中提取參數(shù)。由于這個(gè)原因,在編碼時(shí)最好顯式的指定參數(shù)名。 繼續(xù)@RequestMapping的討論 把@RequestMapping放在類級(jí)別上是合法的,這可令它與方法級(jí)別上的@RequestMapping注解協(xié)同工作,取得縮小選擇范圍的效果,下面是一些例子。 類級(jí)別: RequestMapping("/accounts/*") 方法級(jí)別: @RequestMapping(value="delete", method=RequestMethod.POST)
@RequestMapping(value="index", method=RequestMethod.GET, params="type=checking")
@RequestMapping 第一個(gè)方法級(jí)的請(qǐng)求映射和類級(jí)別的映射結(jié)合,當(dāng)HTTP方法是POST時(shí)與路徑“/accounts/delete”匹配;第二個(gè)添加了一個(gè)要求,就是名為“type”的請(qǐng)求參數(shù)和其值“checking”都需要在請(qǐng)求中出現(xiàn);第三個(gè)根本就沒有指定路徑,這個(gè)方法匹配所有的 HTTP方法,如果有必要的話可以用它的方法名。下面改寫我們的方法,使它可以依靠方法名進(jìn)行匹配,程序如下: @Controller
@RequestMapping("/accounts/*")
public class AccountsController {
??? @RequestMapping(method=RequestMethod.GET)
??? public void show(@RequestParam("number") String number, Map<String, Object> model)
?? {
?????? model.put("account", accountRepository.findAccount(number));
??? }
??? ... 方法匹配的請(qǐng)求是“/accounts/show”,依據(jù)的是類級(jí)別的@RequestMapping指定的匹配路徑“/accounts/*”和方法名“show”。 消除類級(jí)別的請(qǐng)求映射 Web層注解頻遭詬病是有事實(shí)依據(jù)的,那就是嵌入源代碼的URI路徑。這個(gè)問題很好矯正,URI路徑和控制器類之間的匹配關(guān)系用XML配置文件去管理,只在方法級(jí)的映射中使用@RequestMapping注解。 我們將配置一個(gè)ControllerClassNameHandlerMapping,它使用依賴控制器類名字的慣例,將URI映射到控制器: <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> 現(xiàn)在“/accounts/*”這樣的請(qǐng)求都被匹配到AccountsController上,它與方法級(jí)別上的@RequestMapping注解協(xié)作的很好,只要添加上方法名就能夠完成上述映射。此外,既然我們的方法并不會(huì)返回視圖名稱,我們現(xiàn)在就可以依據(jù)慣例匹配類名、方法名、URI路徑和視圖名。 當(dāng)@Controller被完全轉(zhuǎn)換為@MVC后,程序的寫法如下: @Controller
public class AccountsController {
???? private AccountRepository accountRepository;
???? @Autowired
??? public AccountsController(AccountRepository accountRepository) {
?????? this.accountRepository = accountRepository;
??? }
???? @RequestMapping(method=RequestMethod.GET)
??? public void show(@RequestParam("number") String number, Map<String, Object> model)
?? {
?????? model.put("account", accountRepository.findAccount(number));
??? }
??? ... 對(duì)應(yīng)的XML配置文件如下: <context:component-scan base-package="com.abc.accounts"/>
? <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>
? <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
??? <property name="prefix" value="/WEB-INF/views/" />
??? <property name="suffix" value=".jsp" />
</bean> 你可以看出這是一個(gè)最精減的XML。程序里注解中沒有嵌入URI路徑,也沒有顯式指定視圖名,請(qǐng)求處理方法也只有很簡單的一行,方法簽名與我們的需求精準(zhǔn)匹配,其它的請(qǐng)求處理方法也很容易添加。不需要基類,也不需要XML(至少也是沒有直接配置控制器),我們就能獲得上述所有優(yōu)勢(shì)。 也許接下來你就可以看到,這種程序設(shè)計(jì)模型是多么有效了。 @MVC表單處理 一個(gè)典型的表單處理場景包括:獲得可編輯對(duì)象,在編輯模式下顯示它持有的數(shù)據(jù)、允許用戶提交并最終進(jìn)行驗(yàn)證和保存變化數(shù)據(jù)。Spring MVC提供下列幾個(gè)特性輔助進(jìn)行上述所有活動(dòng):數(shù)據(jù)綁定機(jī)制,完全用從請(qǐng)求參數(shù)中獲得的數(shù)據(jù)填充一個(gè)對(duì)象;支持錯(cuò)誤處理和驗(yàn)證;JSP表單標(biāo)記庫;基類控制器。使用@MVC,除了由于@ModelAttribute、@InitBinder和@SessionAttributes這些注解的存在而不再需要基類控制器外,其它一切都不需要改變。 @ModelAttribute注解 看一下這些請(qǐng)求處理方法簽名: @RequestMapping(method=RequestMethod.GET)
public Account setupForm() {
??? ...
}
? @RequestMapping(method=RequestMethod.POST)
public void onSubmit(Account account) {
??? ...
} 它們是非常有效的請(qǐng)求處理方法簽名。第一個(gè)方法處理初始的HTTP GET請(qǐng)求,準(zhǔn)備被編輯的數(shù)據(jù),返回一個(gè)Account對(duì)象供Spring MVC表單標(biāo)簽使用。第二個(gè)方法在用戶提交更改時(shí)處理隨后的HTTP POST請(qǐng)求,并接收一個(gè)Account對(duì)象作為輸入?yún)?shù),它是Spring MVC的數(shù)據(jù)綁定機(jī)制用請(qǐng)求中的參數(shù)自動(dòng)填充的。這是一個(gè)非常簡單的程序模型。 Account對(duì)象中含有要被編輯的數(shù)據(jù)。在Spring MVC的術(shù)語當(dāng)中,Account被稱作是表單模型對(duì)象。這個(gè)對(duì)象必須通過某個(gè)名稱讓表單標(biāo)簽(還有數(shù)據(jù)綁定機(jī)制)知道它的存在。下面是從JSP頁面中截取的部分代碼,引用了一個(gè)名為“account”的表單模型對(duì)象: <form:form modelAttribute="account" method="post"> ??? Account Number: <form:input path="number"/><form:errors path="number"/>
??? ...
?</form> 即使我們沒有在任何地方指定“account”的名稱,這段JSP程序也會(huì)和上面所講的方法簽名協(xié)作的很好。這是因?yàn)?span lang="en-us">@MVC用返回對(duì)象的類型名稱作為默認(rèn)值,因此一個(gè)Account類型的對(duì)象默認(rèn)的就對(duì)應(yīng)一個(gè)名為“account”的表單模型對(duì)象。如果默認(rèn)的不合適,我們就可以用 @ModelAttribute來改變它的名稱,如下所示: @RequestMapping(method=RequestMethod.GET)
public @ModelAttribute("account") SpecialAccount setupForm() {
??? ...
}
? @RequestMapping(method=RequestMethod.POST)
public void update(@ModelAttribute("account") SpecialAccount account) {
??? ...
} @ModelAttribute同樣也可放在方法級(jí)的位置上,取得的效果稍有不同: @ModelAttribute
public Account setupModelAttribute() {
??? ...
} 此處setupModelAttribute()不是一個(gè)請(qǐng)求處理方法,而是任何請(qǐng)求處理方法被調(diào)用之前,用來準(zhǔn)備表單項(xiàng)模型對(duì)象的一個(gè)方法。對(duì)那些熟悉 Spring MVC的老用戶來說,這和SimpleFormController的formBackingObject()方法是非常相似的。 最初的GET方法中我們得到一次表單模型對(duì)象,在隨后的POST方法中當(dāng)我們依靠數(shù)據(jù)綁定機(jī)制用用戶所做的改變覆蓋已有的Account對(duì)象時(shí),我們會(huì)第二次得到它,在這種表單處理場景中把@ModelAttribute放在方法上是很有用的。當(dāng)然,作為一種兩次獲得對(duì)象的替換方案,我們也可以在兩次請(qǐng)求過程中將它保存進(jìn)HTTP的會(huì)話(session),這就是我們下面將要分析的情況。 用@SessionAttributes存儲(chǔ)屬性 @SessionAttributes注解可以用來指定請(qǐng)求過程中要放進(jìn)session中的表單模型對(duì)象的名稱或類型,下面是一些例子: @Controller
@SessionAttributes("account")
public class AccountFormController {
??? ...
}
? @Controller
@SessionAttributes(types = Account.class)
public class AccountFormController {
??? ...
} 根據(jù)上面的注解,AccountFormController會(huì)在初始的GET方法和隨后的POST方法之間,把名為 “account”的表單模型對(duì)象(或者象第二個(gè)例子中的那樣,把所有Account類型的表單模型對(duì)象)存入HTTP會(huì)話(session)中。不過,當(dāng)有改變連續(xù)發(fā)生的時(shí)候,就應(yīng)當(dāng)把屬性對(duì)象從會(huì)話中移除了。我們可以借助SessionStatus實(shí)例來做這件事,如果把它添加進(jìn)onSubmit的方法簽名中,@MVC會(huì)完成這個(gè)任務(wù): @RequestMapping(method=RequestMethod.POST)
public void onSubmit(Account account, SessionStatus sessionStatus) {
??? ...
??? sessionStatus.setComplete(); // Clears @SessionAttributes
} 定制數(shù)據(jù)綁定 有時(shí)數(shù)據(jù)綁定需要定制,例如我們也許需要指定必需填寫的域,或者需要為日期、貨幣金額等類似事情注冊(cè)定制的PropertyEditors。用@MVC實(shí)現(xiàn)這些功能是非常容易的: @InitBinder
public void initDataBinder(WebDataBinder binder) {
??? binder.setRequiredFields(new String[] {"number", "name"});
} @InitBinder注解的方法可以訪問@MVC用來綁定請(qǐng)求參數(shù)的DataBinder實(shí)例,它允許我們?yōu)槊總€(gè)控制器定制必須項(xiàng)。 數(shù)據(jù)綁定結(jié)果和驗(yàn)證 數(shù)據(jù)綁定也許會(huì)導(dǎo)致類似于類型轉(zhuǎn)換或域缺失的錯(cuò)誤。不管發(fā)生什么錯(cuò)誤,我們都希望能返回到編輯的表單,讓用戶自行更正。要想實(shí)現(xiàn)這個(gè)目的,我們可直接在方法簽名的表單模型對(duì)象后面追加一個(gè)BindingResult對(duì)象,例程如下: @RequestMapping(method=RequestMethod.POST)
public ModelAndView onSubmit(Account account, BindingResult bindingResult) {
??? if (bindingResult.hasErrors()) {
?????? ModelAndView mav = new ModelAndView();
?????? mav.getModel().putAll(bindingResult.getModel());
?????? return mav;
??? }
??? // Save the changes and redirect to the next view...
} 發(fā)生錯(cuò)誤時(shí)我們返回到出現(xiàn)問題的視圖,并把從BindingResult得到的屬性增加到模型上,這樣特定域的錯(cuò)誤就能夠反饋給用戶。要注意的是,我們并沒有指定一個(gè)顯式的視圖名,而是允許DispatcherServlet依靠與入口URI路徑信息匹配的默認(rèn)視圖名。 調(diào)用Validator對(duì)象并把BindingResult傳給它,僅這一行代碼就可實(shí)現(xiàn)驗(yàn)證操作。這允許我們?cè)谝粋€(gè)地方收集綁定和驗(yàn)證錯(cuò)誤: @RequestMapping(method=RequestMethod.POST)
public ModelAndView onSubmit(Account account, BindingResult bindingResult) {
??? accountValidator.validate(account, bindingResult);
??? if (bindingResult.hasErrors()) {
?????? ModelAndView mav = new ModelAndView();
?????? mav.getModel().putAll(bindingResult.getModel());
?????? return mav;
??? }
??? // Save the changes and redirect to the next view...
} 現(xiàn)在是時(shí)候結(jié)束我們的Spring 2.5 Web層注解(非正式稱法為@MVC)之旅了。 總結(jié) Web層的注解已經(jīng)證明是相當(dāng)有用的,不僅是因?yàn)樗軌虼蟠鬁p少XML配置文件的數(shù)量,而且還在于它能成就一個(gè)可自由訪問 Spring MVC控制器技術(shù)的精致、靈活和簡潔的程序設(shè)計(jì)模型。我們強(qiáng)烈推薦使用“慣例優(yōu)先原則(convention-over-configuration)” 特性,以及以處理器映射為中心的策略給控制器派發(fā)請(qǐng)求,避免在源碼中嵌入URI路徑或是定義顯式的視圖名引用。 最后是本文沒有討論,但值得關(guān)注的一些非常重要的Spring MVC擴(kuò)展。最新發(fā)布的Spring Web Flow版本2添加了一些特性,例如基于JSF視圖的Spring MVC、Spring的JavaScript庫,還有支持更先進(jìn)編輯場景的高級(jí)狀態(tài)和導(dǎo)航管理。
public class ClinicController {
??? private final Clinic clinic;
??? @Autowired
??? public ClinicController(Clinic clinic) {
?????? this.clinic = clinic;
??? }
??? ... @Controller表明ClinicController是Web層組件,@Autowired請(qǐng)求一個(gè)被依賴注入的Clinic實(shí)例。這個(gè)例子只需要少量的XML語句就能使容器識(shí)別兩個(gè)注解,并限定組件的掃描范圍: <context:component-scan base-package="org.springframework.samples.petclinic"/> 這對(duì)Web層可謂是個(gè)福音,因?yàn)樵谶@層Spring的XML配置文件已日益臃腫,甚至可能還不如層下的配置來得有用。控制器掌握著許多屬性,例如視圖名稱、表單對(duì)象名稱和驗(yàn)證器類型,這些多是關(guān)乎配置的,甚少關(guān)于依賴注入的。通過bean定義繼承,或者避免配置變化不是很頻繁的屬性,也可以有效的管理類似的配置。不過以我的經(jīng)驗(yàn),很多開發(fā)人員都不會(huì)這樣做,結(jié)果就是XML文件總比實(shí)際需要的要龐大。不過 @Controller和@Autowired對(duì)Web層的配置會(huì)產(chǎn)生積極的作用。 在系列文章的第二部分我們將繼續(xù)討論這個(gè)問題,并瀏覽Spring 2.5在Web層的注解技術(shù)。這些注解被非正式的稱為@MVC,它涉及到了Spring MVC和Spring Porlet MVC,實(shí)際上本文討論的大部分功能都可以應(yīng)用在這兩個(gè)框架上。 從Controller到@Controller 與第一部分討論的注解相比,@MVC已不只是作為配置的一種替換方案這樣簡單了,考慮下面這個(gè)著名的Spring MVC控制器簽名: public interface Controller {
??? ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse
response) throws Exception;
} 所有的Spring MVC控制器要么直接實(shí)現(xiàn)Controller接口,要么就得擴(kuò)展類似AbstractController、 SimpleFormController、 MultiActionController或AbstractWizardFormController這樣的基類實(shí)現(xiàn)。正是Controller接口允許Spring MVC的DispatcherServlet把所有上述對(duì)象都看作是“處理器(handlers)”,并在一個(gè)名為 SimpleControllerHandlerAdapter的適配器的幫助下調(diào)用它們。 @MVC從三個(gè)重要的方面改變了這個(gè)程序設(shè)計(jì)模型:
public class AccountsController {
??? private AccountRepository accountRepository;
??? @Autowired
??? public AccountsController(AccountRepository accountRepository) {
?????? this.accountRepository = accountRepository;
??? }
??? @RequestMapping("/accounts/show")
??? public ModelAndView show(HttpServletRequest request,
???????????????????????????? HttpServletResponse response) throws Exception {
?????? String number = ServletRequestUtils.getStringParameter(request, "number");
?????? ModelAndView mav = new ModelAndView("/WEB-INF/views/accounts/show.jsp");
?????? mav.addObject("account", accountRepository.findAccount(number));
?????? return mav;
??? }
} 此處與以往的不同在于,這個(gè)控制器并沒有擴(kuò)展Controller接口,并且它用@RequestMapping注解指明show()是映射到URI路徑 “/accounts/show”的請(qǐng)求處理方法。除此以外,其余代碼都是一個(gè)典型的Spring MVC控制器應(yīng)有的內(nèi)容。 在將上述的方法完全轉(zhuǎn)化到@MVC后,我們會(huì)再回過頭來看@RequestMapping,但是在此之前還有一點(diǎn)需要提請(qǐng)注意,上面的請(qǐng)求映射URI也可匹配帶有任意擴(kuò)展名的URI路徑,例如: /accounts/show.htm
/accounts/show.xls
/accounts/show.pdf
... 靈活的請(qǐng)求處理方法簽名 我們?cè)?jīng)承諾過要提供靈活的方法簽名,現(xiàn)在來看一下成果。輸入的參數(shù)中移除了響應(yīng)對(duì)象,增加了一個(gè)代表模型的Map;返回的不再是ModelAndView,而是一個(gè)字符串,指明呈現(xiàn)響應(yīng)時(shí)要用的視圖名字: @RequestMapping("/accounts/show")
public String show(HttpServletRequest request, Map<String, Object> model)
throws Exception {
??? String number = ServletRequestUtils.getStringParameter(request, "number");
??? model.put("account", accountRepository.findAccount(number));
??? return "/WEB-INF/views/accounts/show.jsp";
} Map輸入?yún)?shù)是一個(gè)“隱式的”模型,對(duì)于我們來說在調(diào)用方法前創(chuàng)建它很方便,其中添加的鍵—值對(duì)數(shù)據(jù)便于在視圖中解析應(yīng)用。本例視圖為show.jsp頁面。 @MVC可以接受多種類型的輸入?yún)?shù),例如 HttpServletRequest/HttpServletResponse、HttpSession、Locale、InputStream、 OutputStream、File[]等等,它們的順序不受任何限制;同樣它也允許多種返回類型,例如ModelAndView、Map、 String,或者什么都不返回。你可以查看@RequestMapping的JavaDoc以了解它支持的所有輸入和返回參數(shù)類型。 有種令人感興趣的情形是當(dāng)方法沒有指定視圖時(shí)(例如返回類型為void)會(huì)有什么事情發(fā)生,按照慣例DispatcherServlet要再使用請(qǐng)求URI的路徑信息,不過要移去前面的斜杠和擴(kuò)展名。讓我們把返回類型改為void: @RequestMapping("/accounts/show")
public void show(HttpServletRequest request, Map<String, Object> model) throws Exception {
??? String number = ServletRequestUtils.getStringParameter(request, "number");
??? model.put("account", accountRepository.findAccount(number));
} 對(duì)于給定的請(qǐng)求處理方法和“/accounts/show”的請(qǐng)求映射,我們可以期望DispatcherServlet能夠獲得“accounts/show”的默認(rèn)視圖名稱,當(dāng)它與如下適當(dāng)?shù)囊晥D解析器結(jié)合共同作用時(shí),會(huì)產(chǎn)生與前面指明返回視圖名同樣的結(jié)果: <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
??? <property name="prefix" value="/WEB-INF/views/" />
??? <property name="suffix" value=".jsp" />
</bean> 強(qiáng)烈推薦視圖名稱依賴慣例的方式,因?yàn)檫@樣可以從控制器代碼中消除硬編碼的視圖名稱。如果你想定制 DispatcherServlet獲取默認(rèn)視圖名的方式,就在servlet上下文環(huán)境中配置一個(gè)你自己的 RequestToViewNameTranslator實(shí)現(xiàn),并為其bean id賦名為“viewNameTranslator”。 用@RequestParam提取和解析參數(shù) @MVC另外一個(gè)特性是其提取和解析請(qǐng)求參數(shù)的能力。讓我們繼續(xù)重構(gòu)上面的方法,并在其中添加@RequestParam注解: @RequestMapping("/accounts/show")
public void show(@RequestParam("number") String number, Map<String, Object> model) {
??? model.put("account", accountRepository.findAccount(number));
} 這里@RequestParam注解可以用來提取名為“number”的String類型的參數(shù),并將之作為輸入?yún)?shù)傳入。 @RequestParam支持類型轉(zhuǎn)換,還有必需和可選參數(shù)。類型轉(zhuǎn)換目前支持所有的基本Java類型,你可通過定制的PropertyEditors 來擴(kuò)展它的范圍。下面是一些例子,其中包括了必需和可選參數(shù): @RequestParam(value="number", required=false) String number
@RequestParam("id") Long id
@RequestParam("balance") double balance
@RequestParam double amount 注意,最后一個(gè)例子沒有提供清晰的參數(shù)名。當(dāng)且僅當(dāng)代碼帶調(diào)試符號(hào)編譯時(shí),結(jié)果會(huì)提取名為“amount ”的參數(shù),否則,將拋出IllegalStateException異常,因?yàn)楫?dāng)前的信息不足以從請(qǐng)求中提取參數(shù)。由于這個(gè)原因,在編碼時(shí)最好顯式的指定參數(shù)名。 繼續(xù)@RequestMapping的討論 把@RequestMapping放在類級(jí)別上是合法的,這可令它與方法級(jí)別上的@RequestMapping注解協(xié)同工作,取得縮小選擇范圍的效果,下面是一些例子。 類級(jí)別: RequestMapping("/accounts/*") 方法級(jí)別: @RequestMapping(value="delete", method=RequestMethod.POST)
@RequestMapping(value="index", method=RequestMethod.GET, params="type=checking")
@RequestMapping 第一個(gè)方法級(jí)的請(qǐng)求映射和類級(jí)別的映射結(jié)合,當(dāng)HTTP方法是POST時(shí)與路徑“/accounts/delete”匹配;第二個(gè)添加了一個(gè)要求,就是名為“type”的請(qǐng)求參數(shù)和其值“checking”都需要在請(qǐng)求中出現(xiàn);第三個(gè)根本就沒有指定路徑,這個(gè)方法匹配所有的 HTTP方法,如果有必要的話可以用它的方法名。下面改寫我們的方法,使它可以依靠方法名進(jìn)行匹配,程序如下: @Controller
@RequestMapping("/accounts/*")
public class AccountsController {
??? @RequestMapping(method=RequestMethod.GET)
??? public void show(@RequestParam("number") String number, Map<String, Object> model)
?? {
?????? model.put("account", accountRepository.findAccount(number));
??? }
??? ... 方法匹配的請(qǐng)求是“/accounts/show”,依據(jù)的是類級(jí)別的@RequestMapping指定的匹配路徑“/accounts/*”和方法名“show”。 消除類級(jí)別的請(qǐng)求映射 Web層注解頻遭詬病是有事實(shí)依據(jù)的,那就是嵌入源代碼的URI路徑。這個(gè)問題很好矯正,URI路徑和控制器類之間的匹配關(guān)系用XML配置文件去管理,只在方法級(jí)的映射中使用@RequestMapping注解。 我們將配置一個(gè)ControllerClassNameHandlerMapping,它使用依賴控制器類名字的慣例,將URI映射到控制器: <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> 現(xiàn)在“/accounts/*”這樣的請(qǐng)求都被匹配到AccountsController上,它與方法級(jí)別上的@RequestMapping注解協(xié)作的很好,只要添加上方法名就能夠完成上述映射。此外,既然我們的方法并不會(huì)返回視圖名稱,我們現(xiàn)在就可以依據(jù)慣例匹配類名、方法名、URI路徑和視圖名。 當(dāng)@Controller被完全轉(zhuǎn)換為@MVC后,程序的寫法如下: @Controller
public class AccountsController {
???? private AccountRepository accountRepository;
???? @Autowired
??? public AccountsController(AccountRepository accountRepository) {
?????? this.accountRepository = accountRepository;
??? }
???? @RequestMapping(method=RequestMethod.GET)
??? public void show(@RequestParam("number") String number, Map<String, Object> model)
?? {
?????? model.put("account", accountRepository.findAccount(number));
??? }
??? ... 對(duì)應(yīng)的XML配置文件如下: <context:component-scan base-package="com.abc.accounts"/>
? <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>
? <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
??? <property name="prefix" value="/WEB-INF/views/" />
??? <property name="suffix" value=".jsp" />
</bean> 你可以看出這是一個(gè)最精減的XML。程序里注解中沒有嵌入URI路徑,也沒有顯式指定視圖名,請(qǐng)求處理方法也只有很簡單的一行,方法簽名與我們的需求精準(zhǔn)匹配,其它的請(qǐng)求處理方法也很容易添加。不需要基類,也不需要XML(至少也是沒有直接配置控制器),我們就能獲得上述所有優(yōu)勢(shì)。 也許接下來你就可以看到,這種程序設(shè)計(jì)模型是多么有效了。 @MVC表單處理 一個(gè)典型的表單處理場景包括:獲得可編輯對(duì)象,在編輯模式下顯示它持有的數(shù)據(jù)、允許用戶提交并最終進(jìn)行驗(yàn)證和保存變化數(shù)據(jù)。Spring MVC提供下列幾個(gè)特性輔助進(jìn)行上述所有活動(dòng):數(shù)據(jù)綁定機(jī)制,完全用從請(qǐng)求參數(shù)中獲得的數(shù)據(jù)填充一個(gè)對(duì)象;支持錯(cuò)誤處理和驗(yàn)證;JSP表單標(biāo)記庫;基類控制器。使用@MVC,除了由于@ModelAttribute、@InitBinder和@SessionAttributes這些注解的存在而不再需要基類控制器外,其它一切都不需要改變。 @ModelAttribute注解 看一下這些請(qǐng)求處理方法簽名: @RequestMapping(method=RequestMethod.GET)
public Account setupForm() {
??? ...
}
? @RequestMapping(method=RequestMethod.POST)
public void onSubmit(Account account) {
??? ...
} 它們是非常有效的請(qǐng)求處理方法簽名。第一個(gè)方法處理初始的HTTP GET請(qǐng)求,準(zhǔn)備被編輯的數(shù)據(jù),返回一個(gè)Account對(duì)象供Spring MVC表單標(biāo)簽使用。第二個(gè)方法在用戶提交更改時(shí)處理隨后的HTTP POST請(qǐng)求,并接收一個(gè)Account對(duì)象作為輸入?yún)?shù),它是Spring MVC的數(shù)據(jù)綁定機(jī)制用請(qǐng)求中的參數(shù)自動(dòng)填充的。這是一個(gè)非常簡單的程序模型。 Account對(duì)象中含有要被編輯的數(shù)據(jù)。在Spring MVC的術(shù)語當(dāng)中,Account被稱作是表單模型對(duì)象。這個(gè)對(duì)象必須通過某個(gè)名稱讓表單標(biāo)簽(還有數(shù)據(jù)綁定機(jī)制)知道它的存在。下面是從JSP頁面中截取的部分代碼,引用了一個(gè)名為“account”的表單模型對(duì)象: <form:form modelAttribute="account" method="post"> ??? Account Number: <form:input path="number"/><form:errors path="number"/>
??? ...
?</form> 即使我們沒有在任何地方指定“account”的名稱,這段JSP程序也會(huì)和上面所講的方法簽名協(xié)作的很好。這是因?yàn)?span lang="en-us">@MVC用返回對(duì)象的類型名稱作為默認(rèn)值,因此一個(gè)Account類型的對(duì)象默認(rèn)的就對(duì)應(yīng)一個(gè)名為“account”的表單模型對(duì)象。如果默認(rèn)的不合適,我們就可以用 @ModelAttribute來改變它的名稱,如下所示: @RequestMapping(method=RequestMethod.GET)
public @ModelAttribute("account") SpecialAccount setupForm() {
??? ...
}
? @RequestMapping(method=RequestMethod.POST)
public void update(@ModelAttribute("account") SpecialAccount account) {
??? ...
} @ModelAttribute同樣也可放在方法級(jí)的位置上,取得的效果稍有不同: @ModelAttribute
public Account setupModelAttribute() {
??? ...
} 此處setupModelAttribute()不是一個(gè)請(qǐng)求處理方法,而是任何請(qǐng)求處理方法被調(diào)用之前,用來準(zhǔn)備表單項(xiàng)模型對(duì)象的一個(gè)方法。對(duì)那些熟悉 Spring MVC的老用戶來說,這和SimpleFormController的formBackingObject()方法是非常相似的。 最初的GET方法中我們得到一次表單模型對(duì)象,在隨后的POST方法中當(dāng)我們依靠數(shù)據(jù)綁定機(jī)制用用戶所做的改變覆蓋已有的Account對(duì)象時(shí),我們會(huì)第二次得到它,在這種表單處理場景中把@ModelAttribute放在方法上是很有用的。當(dāng)然,作為一種兩次獲得對(duì)象的替換方案,我們也可以在兩次請(qǐng)求過程中將它保存進(jìn)HTTP的會(huì)話(session),這就是我們下面將要分析的情況。 用@SessionAttributes存儲(chǔ)屬性 @SessionAttributes注解可以用來指定請(qǐng)求過程中要放進(jìn)session中的表單模型對(duì)象的名稱或類型,下面是一些例子: @Controller
@SessionAttributes("account")
public class AccountFormController {
??? ...
}
? @Controller
@SessionAttributes(types = Account.class)
public class AccountFormController {
??? ...
} 根據(jù)上面的注解,AccountFormController會(huì)在初始的GET方法和隨后的POST方法之間,把名為 “account”的表單模型對(duì)象(或者象第二個(gè)例子中的那樣,把所有Account類型的表單模型對(duì)象)存入HTTP會(huì)話(session)中。不過,當(dāng)有改變連續(xù)發(fā)生的時(shí)候,就應(yīng)當(dāng)把屬性對(duì)象從會(huì)話中移除了。我們可以借助SessionStatus實(shí)例來做這件事,如果把它添加進(jìn)onSubmit的方法簽名中,@MVC會(huì)完成這個(gè)任務(wù): @RequestMapping(method=RequestMethod.POST)
public void onSubmit(Account account, SessionStatus sessionStatus) {
??? ...
??? sessionStatus.setComplete(); // Clears @SessionAttributes
} 定制數(shù)據(jù)綁定 有時(shí)數(shù)據(jù)綁定需要定制,例如我們也許需要指定必需填寫的域,或者需要為日期、貨幣金額等類似事情注冊(cè)定制的PropertyEditors。用@MVC實(shí)現(xiàn)這些功能是非常容易的: @InitBinder
public void initDataBinder(WebDataBinder binder) {
??? binder.setRequiredFields(new String[] {"number", "name"});
} @InitBinder注解的方法可以訪問@MVC用來綁定請(qǐng)求參數(shù)的DataBinder實(shí)例,它允許我們?yōu)槊總€(gè)控制器定制必須項(xiàng)。 數(shù)據(jù)綁定結(jié)果和驗(yàn)證 數(shù)據(jù)綁定也許會(huì)導(dǎo)致類似于類型轉(zhuǎn)換或域缺失的錯(cuò)誤。不管發(fā)生什么錯(cuò)誤,我們都希望能返回到編輯的表單,讓用戶自行更正。要想實(shí)現(xiàn)這個(gè)目的,我們可直接在方法簽名的表單模型對(duì)象后面追加一個(gè)BindingResult對(duì)象,例程如下: @RequestMapping(method=RequestMethod.POST)
public ModelAndView onSubmit(Account account, BindingResult bindingResult) {
??? if (bindingResult.hasErrors()) {
?????? ModelAndView mav = new ModelAndView();
?????? mav.getModel().putAll(bindingResult.getModel());
?????? return mav;
??? }
??? // Save the changes and redirect to the next view...
} 發(fā)生錯(cuò)誤時(shí)我們返回到出現(xiàn)問題的視圖,并把從BindingResult得到的屬性增加到模型上,這樣特定域的錯(cuò)誤就能夠反饋給用戶。要注意的是,我們并沒有指定一個(gè)顯式的視圖名,而是允許DispatcherServlet依靠與入口URI路徑信息匹配的默認(rèn)視圖名。 調(diào)用Validator對(duì)象并把BindingResult傳給它,僅這一行代碼就可實(shí)現(xiàn)驗(yàn)證操作。這允許我們?cè)谝粋€(gè)地方收集綁定和驗(yàn)證錯(cuò)誤: @RequestMapping(method=RequestMethod.POST)
public ModelAndView onSubmit(Account account, BindingResult bindingResult) {
??? accountValidator.validate(account, bindingResult);
??? if (bindingResult.hasErrors()) {
?????? ModelAndView mav = new ModelAndView();
?????? mav.getModel().putAll(bindingResult.getModel());
?????? return mav;
??? }
??? // Save the changes and redirect to the next view...
} 現(xiàn)在是時(shí)候結(jié)束我們的Spring 2.5 Web層注解(非正式稱法為@MVC)之旅了。 總結(jié) Web層的注解已經(jīng)證明是相當(dāng)有用的,不僅是因?yàn)樗軌虼蟠鬁p少XML配置文件的數(shù)量,而且還在于它能成就一個(gè)可自由訪問 Spring MVC控制器技術(shù)的精致、靈活和簡潔的程序設(shè)計(jì)模型。我們強(qiáng)烈推薦使用“慣例優(yōu)先原則(convention-over-configuration)” 特性,以及以處理器映射為中心的策略給控制器派發(fā)請(qǐng)求,避免在源碼中嵌入URI路徑或是定義顯式的視圖名引用。 最后是本文沒有討論,但值得關(guān)注的一些非常重要的Spring MVC擴(kuò)展。最新發(fā)布的Spring Web Flow版本2添加了一些特性,例如基于JSF視圖的Spring MVC、Spring的JavaScript庫,還有支持更先進(jìn)編輯場景的高級(jí)狀態(tài)和導(dǎo)航管理。
?
轉(zhuǎn)載于:https://blog.51cto.com/tonyaction/190071
總結(jié)
以上是生活随笔為你收集整理的Spring 2.5:Spring MVC中的新特性的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Oracle rac进阶管理专家指导系列
- 下一篇: (原创)JS兼容性笔记(更新)