Spring注解使用方法
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層配置和單元/集成測(cè)試。
探索Spring 2.5中引入的注解技術(shù)系列文章由三部分組成,本文是其中的第二篇,它主要講述了Web層中的注解支持。最后一篇文章將著重介紹可用于集成和測(cè)試的其它特性。
這個(gè)系列文章的第一部分論述了Java注解(annotation)是如何代替XML來配置Spring管理對(duì)象和依賴注入的。我們?cè)儆靡粋€(gè)例子回顧一下:
@Controllerpublic class ClinicController {private final Clinic clinic;@Autowiredpublic ClinicController(Clinic clinic) {this.clinic = clinic;}...@Controller表明ClinicController是Web層組件,@Autowired請(qǐng)求一個(gè)被依賴注入的Clinic實(shí)例。這個(gè)例子只需要少量的XML語(yǔ)句就能使容器識(shí)別兩個(gè)注解,并限定組件的掃描范圍:
<context:component-scan base-package="org.springframework.samples.petclinic"/>這對(duì)Web層可謂是個(gè)福音,因?yàn)樵谶@層Spring的XML配置文件已日益臃腫,甚至可能還不如層下的配置來得有用??刂破髡莆罩S多屬性,例如視圖名稱、表單對(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已不只是作為配置的一種替換方案這樣簡(jiǎn)單了,考慮下面這個(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ì)模型:
考慮到以上三個(gè)要點(diǎn),就可以說很公平的說@MVC不僅僅是個(gè)替換方案了,它將會(huì)是Spring MVC的控制器技術(shù)演變過程中下一個(gè)重要步驟。
DispatcherServlet在名為AnnotationMethodHandlerAdapter的適配器幫助下調(diào)用被注解的控制器。正是這個(gè)適配器做了大量工作支持我們此后將會(huì)討論的注解,同時(shí)也是它有效的取代了對(duì)于控制器基類的需求。
@RequestMapping簡(jiǎn)介
我們還是從一個(gè)類似于傳統(tǒng)的Spring MVC Controller控制器開始:
@Controllerpublic class AccountsController {private AccountRepository accountRepository;@Autowiredpublic 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頁(yè)面。
@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后,程序的寫法如下:
@Controllerpublic class AccountsController {private AccountRepository accountRepository;@Autowiredpublic 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。程序里注解中沒有嵌入U(xiǎn)RI路徑,也沒有顯式指定視圖名,請(qǐng)求處理方法也只有很簡(jiǎn)單的一行,方法簽名與我們的需求精準(zhǔn)匹配,其它的請(qǐng)求處理方法也很容易添加。不需要基類,也不需要XML(至少也是沒有直接配置控制器),我們就能獲得上述所有優(yōu)勢(shì)。
也許接下來你就可以看到,這種程序設(shè)計(jì)模型是多么有效了。
@MVC表單處理
一個(gè)典型的表單處理場(chǎng)景包括:獲得可編輯對(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)記庫(kù);基類控制器。使用@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è)非常簡(jiǎn)單的程序模型。
Account對(duì)象中含有要被編輯的數(shù)據(jù)。在Spring MVC的術(shù)語(yǔ)當(dāng)中,Account被稱作是表單模型對(duì)象。這個(gè)對(duì)象必須通過某個(gè)名稱讓表單標(biāo)簽(還有數(shù)據(jù)綁定機(jī)制)知道它的存在。下面是從JSP頁(yè)面中截取的部分代碼,引用了一個(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)?#64;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í)的位置上,取得的效果稍有不同:
@ModelAttributepublic 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ì)第二次得到它,在這種表單處理場(chǎng)景中把@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)這些功能是非常容易的:
@InitBinderpublic 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ù)的精致、靈活和簡(jiǎn)潔的程序設(shè)計(jì)模型。我們強(qiáng)烈推薦使用“慣例優(yōu)先原則(convention-over-configuration)” 特性,以及以處理器映射為中心的策略給控制器派發(fā)請(qǐng)求,避免在源碼中嵌入U(xiǎn)RI路徑或是定義顯式的視圖名引用。
最后是本文沒有討論,但值得關(guān)注的一些非常重要的Spring MVC擴(kuò)展。最新發(fā)布的Spring Web Flow版本2添加了一些特性,例如基于JSF視圖的Spring MVC、Spring的JavaScript庫(kù),還有支持更先進(jìn)編輯場(chǎng)景的高級(jí)狀態(tài)和導(dǎo)航管理。
查看英文原文:Spring 2.5: New Features in Spring MVC。
總結(jié)
以上是生活随笔為你收集整理的Spring注解使用方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2012_01_26
- 下一篇: 使用JS在textarea在光标处插入内