Spring的模型-視圖-控制器(MVC)框架是圍繞一個(gè)DispatcherServlet來(lái)設(shè)計(jì)的,這個(gè)Servlet會(huì)把請(qǐng)求分發(fā)給各個(gè)處理器,并支持可配置的處理器映射、
視圖渲染、本地化、時(shí)區(qū)與主題渲染等,甚至還能支持文件上傳。處理器是你的應(yīng)用中注解了@Controller和@RequestMapping的類(lèi)和方法,
Spring為處理器方法提供了極其多樣靈活的配置。Spring 3.0以后提供了@Controller注解機(jī)制、@PathVariable注解以及一些其他的特性,
你可以使用它們來(lái)進(jìn)行RESTful web站點(diǎn)和應(yīng)用的開(kāi)發(fā)。
在Spring Web MVC中,你可以使用任何對(duì)象來(lái)作為命令對(duì)象或表單返回對(duì)象,而無(wú)須實(shí)現(xiàn)一個(gè)框架相關(guān)的接口或基類(lèi)。Spring的數(shù)據(jù)綁定非常靈活:比如,
它會(huì)把數(shù)據(jù)類(lèi)型不匹配當(dāng)成可由應(yīng)用自行處理的運(yùn)行時(shí)驗(yàn)證錯(cuò)誤,而非系統(tǒng)錯(cuò)誤。你可能會(huì)為了避免非法的類(lèi)型轉(zhuǎn)換在表單對(duì)象中使用字符串來(lái)存儲(chǔ)數(shù)據(jù),
但無(wú)類(lèi)型的字符串無(wú)法描述業(yè)務(wù)數(shù)據(jù)的真正含義,并且你還需要把它們轉(zhuǎn)換成對(duì)應(yīng)的業(yè)務(wù)對(duì)象類(lèi)型。有了Spring的驗(yàn)證機(jī)制,意味著你再也不需這么做了,
并且直接將業(yè)務(wù)對(duì)象綁定到表單對(duì)象上通常是更好的選擇。
Spring的視圖解析也是設(shè)計(jì)得異常靈活??刂破饕话阖?fù)責(zé)準(zhǔn)備一個(gè)Map模型、填充數(shù)據(jù)、返回一個(gè)合適的視圖名等,同時(shí)它也可以直接將數(shù)據(jù)寫(xiě)到響應(yīng)流中。
視圖名的解析高度靈活,支持多種配置,包括通過(guò)文件擴(kuò)展名、Accept內(nèi)容頭、bean、配置文件等的配置,甚至你還可以自己實(shí)現(xiàn)一個(gè)視圖解析器ViewResolver。
模型(MVC中的M,model)其實(shí)是一個(gè)Map類(lèi)型的接口,徹底地把數(shù)據(jù)從視圖技術(shù)中抽象分離了出來(lái)。你可以與基于模板的渲染技術(shù)直接整合,
如JSP、Velocity和Freemarker等,或者你還可以直接生成XML、JSON、Atom以及其他多種類(lèi)型的內(nèi)容。Map模型會(huì)簡(jiǎn)單地被轉(zhuǎn)換成合適的格式,
比如JSP的請(qǐng)求屬性(attribute),一個(gè)Velocity模板的模型等。
1 @Controller
@Controller注解可以認(rèn)為是被標(biāo)注類(lèi)的原型(stereotype),表明了這個(gè)類(lèi)所承擔(dān)的角色。
分派器(DispatcherServlet)會(huì)掃描所有注解了@Controller的類(lèi),檢測(cè)其中通過(guò)@RequestMapping注解配置的方法
2 @RequestMapping
你可以使用@RequestMapping注解來(lái)將請(qǐng)求URL,如/appointments等,映射到整個(gè)類(lèi)上或某個(gè)特定的處理器方法上。一般來(lái)說(shuō),
類(lèi)級(jí)別的注解負(fù)責(zé)將一個(gè)特定(或符合某種模式)的請(qǐng)求路徑映射到一個(gè)控制器上,
同時(shí)通過(guò)方法級(jí)別的注解來(lái)細(xì)化映射,即根據(jù)特定的HTTP請(qǐng)求方法(“GET”“POST”方法等)、HTTP請(qǐng)求中是否攜帶特定參數(shù)等條件,將請(qǐng)求映射到匹配的方法上。
例如:
@Controller
@RequestMapping("/appointments")
public class AppointmentsController {
private final AppointmentBook appointmentBook;
@Autowired
public AppointmentsController(AppointmentBook appointmentBook) {
this.appointmentBook = appointmentBook;
}
@RequestMapping(method = RequestMethod.GET)
public Map get() {
return appointmentBook.getAppointmentsForToday();
}
@RequestMapping(path = "/{day}", method = RequestMethod.GET)
public Map getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
return appointmentBook.getAppointmentsForDay(day);
}
@RequestMapping(path = "/new", method = RequestMethod.GET)
public AppointmentForm getNewForm() {
return new AppointmentForm();
}
@RequestMapping(method = RequestMethod.POST)
public String add(@Valid AppointmentForm appointment, BindingResult result) {
if (result.hasErrors()) {
return "appointments/new";
}
appointmentBook.addAppointment(appointment);
return "redirect:/appointments";
}
}
在上面的示例中,許多地方都使用到了@RequestMapping注解。第一次使用點(diǎn)是作用于類(lèi)級(jí)別的,它指示了所有/appointments開(kāi)頭的路徑都會(huì)被映射到控制器下。
get()方法上的@RequestMapping注解對(duì)請(qǐng)求路徑進(jìn)行了進(jìn)一步細(xì)化:它僅接受GET方法的請(qǐng)求。這樣,一個(gè)請(qǐng)求路徑為/appointments、HTTP方法為GET的請(qǐng)求,
將會(huì)最終進(jìn)入到這個(gè)方法被處理。add()方法也做了類(lèi)似的細(xì)化,而getNewForm()方法則同時(shí)注解了能夠接受的請(qǐng)求的HTTP方法和路徑。這種情況下,
一個(gè)路徑為appointments/new、HTTP方法為GET的請(qǐng)求將會(huì)被這個(gè)方法所處理。getForDay()方法則展示了使用@RequestMapping注解的另一個(gè)技巧:URI模板。
2.1 URI模板
URI模板是一個(gè)類(lèi)似于URI的字符串,只不過(guò)其中包含了一個(gè)或多個(gè)的變量名。當(dāng)你使用實(shí)際的值去填充這些變量名的時(shí)候,模板就退化成了一個(gè)URI。
在URI模板的RFC提議中定義了一個(gè)URI是如何進(jìn)行參數(shù)化的。比如說(shuō),一個(gè)這個(gè)URI模板http://www.example.com/users/{userId}就包含了一個(gè)變量名userId。
將值fred賦給這個(gè)變量名后,它就變成了一個(gè)URI:http://www.example.com/users/fred
2.2 @PathVariable
在Spring MVC中你可以在方法參數(shù)上使用@PathVariable注解,將其與URI模板中的參數(shù)綁定起來(lái):
@RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
return "displayOwner";
}
URI模板"/owners/{ownerId}"指定了一個(gè)變量,名為ownerId。當(dāng)控制器處理這個(gè)請(qǐng)求的時(shí)候,ownerId的值就會(huì)被URI模板中對(duì)應(yīng)部分的值所填充。
比如說(shuō),如果請(qǐng)求的URI是/owners/fred,此時(shí)變量ownerId的值就是fred.
2.2.1 為了處理@PathVariables注解,Spring MVC必須通過(guò)變量名來(lái)找到URI模板中相對(duì)應(yīng)的變量。你可以在注解中直接聲明:
@RequestMapping(path="/owners/{ownerId}}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
// 具體的方法代碼…
}
或者,如果URI模板中的變量名與方法的參數(shù)名是相同的,則你可以不必再指定一次。只要你在編譯的時(shí)候留下debug信息,Spring MVC就可以自動(dòng)匹配URL模板中與方法參數(shù)名相同的變量名。
@RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
// 具體的方法代碼…
}
2.2.2 一個(gè)方法可以擁有任意數(shù)量的@PathVariable注解:
@RequestMapping(path="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
Pet pet = owner.getPet(petId);
model.addAttribute("pet", pet);
return "displayPet";
}
2.2.3 URI模板可以從類(lèi)級(jí)別和方法級(jí)別的 @RequestMapping 注解獲取數(shù)據(jù)。因此,像這樣的findPet()方法可以被類(lèi)似于/owners/42/pets/21這樣的URL路由并調(diào)用到:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping("/pets/{petId}")
public void findPet(@PathVariable_String ownerId, @PathVariable String petId, Model model) {
// 方法實(shí)現(xiàn)體這里忽略
}
}
2.3 帶正則表達(dá)式的URI模板
有時(shí)候你可能需要更準(zhǔn)確地描述一個(gè)URI模板的變量,比如說(shuō)這個(gè)URL:"/spring-web/spring-web-3.0.5.jar。你要怎么把它分解成幾個(gè)有意義的部分呢?
@RequestMapping注解支持你在URI模板變量中使用正則表達(dá)式。語(yǔ)法是{varName:regex},其中第一部分定義了變量名,第二部分就是你所要應(yīng)用的正則表達(dá)式。比如下面的代碼樣例:
@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String extension) {
// 代碼部分省略...
}
}
2.4 Path Patterns
除了URI模板外,@RequestMapping注解還支持Ant風(fēng)格的路徑模式(如/myPath/*.do等)。不僅如此,還可以把URI模板變量和Ant風(fēng)格
的glob組合起來(lái)使用(比如/owners/*/pets/{petId}這樣的用法等)。
2.5 路徑樣式的匹配(Path Pattern Comparison)
2.5.1 當(dāng)一個(gè)URL同時(shí)匹配多個(gè)模板(pattern)時(shí),我們將需要一個(gè)算法來(lái)決定其中最匹配的一個(gè)。
2.5.2 URI模板變量的數(shù)目和通配符數(shù)量的總和最少的那個(gè)路徑模板更準(zhǔn)確。舉個(gè)例子,/hotels/{hotel}/*這個(gè)路徑擁有一個(gè)URI變量和一個(gè)通配符,
而/hotels/{hotel}/**這個(gè)路徑則擁有一個(gè)URI變量和兩個(gè)通配符,因此,我們認(rèn)為前者是更準(zhǔn)確的路徑模板。
2.5.3 如果兩個(gè)模板的URI模板數(shù)量和通配符數(shù)量總和一致,則路徑更長(zhǎng)的那個(gè)模板更準(zhǔn)確。舉個(gè)例子,/foo/bar*就被認(rèn)為比/foo/*更準(zhǔn)確,因?yàn)榍罢叩穆窂礁L(zhǎng)。
2.5.4 如果兩個(gè)模板的數(shù)量和長(zhǎng)度均一致,則那個(gè)具有更少通配符的模板是更加準(zhǔn)確的。比如,/hotels/{hotel}就比/hotels/*更精確。
2.5.5 默認(rèn)的通配模式/**比其他所有的模式都更“不準(zhǔn)確”。比方說(shuō),/api/{a}//{c}就比默認(rèn)的通配模式/**要更準(zhǔn)確
2.5.6 前綴通配(比如/public/**)被認(rèn)為比其他任何不包括雙通配符的模式更不準(zhǔn)確。比如說(shuō),/public/path3/{a}//{c}就比/public/**更準(zhǔn)確
2.6 帶占位符的路徑模式(path patterns)
@RequestMapping注解支持在路徑中使用占位符,以取得一些本地配置、系統(tǒng)配置、環(huán)境變量等。這個(gè)特性有時(shí)很有用,比如說(shuō)控制器的映射路徑需要通過(guò)配置來(lái)定制的場(chǎng)景。
如果想了解更多關(guān)于占位符的信息,可以參考PropertyPlaceholderConfigurer這個(gè)類(lèi)的文檔。
2.7 后綴模式匹配(Suffix Pattern Matching)
Spring MVC默認(rèn)采用".*"的后綴模式匹配來(lái)進(jìn)行路徑匹配,因此,一個(gè)映射到/person路徑的控制器也會(huì)隱式地被映射到/person.*。
這使得通過(guò)URL來(lái)請(qǐng)求同一資源文件的不同格式變得更簡(jiǎn)單(比如/person.pdf,/person.xml)。
你可以關(guān)閉默認(rèn)的后綴模式匹配,或者顯式地將路徑后綴限定到一些特定格式上for content negotiation purpose。我們推薦這樣做,
這樣可以減少映射請(qǐng)求時(shí)可以帶來(lái)的一些二義性,比如請(qǐng)求以下路徑/person/{id}時(shí),路徑中的點(diǎn)號(hào)后面帶的可能不是描述內(nèi)容格式,
比如/person/joe@email.com vs /person/joe@email.com.json。而且正如下面馬上要提到的,后綴模式通配以及內(nèi)容協(xié)商有時(shí)可能會(huì)被黑客用來(lái)進(jìn)行攻擊,
因此,對(duì)后綴通配進(jìn)行有意義的限定是有好處的。
2.8 矩陣變量
使用 @MatrixVariable 注解
2.8.1 原來(lái)的URI規(guī)范RFC 3986中允許在路徑段落中攜帶鍵值對(duì),但規(guī)范沒(méi)有明確給這樣的鍵值對(duì)定義術(shù)語(yǔ)。有人叫“URI路徑參數(shù)”,也有叫“矩陣URI”的。
后者是Tim Berners-Lee首先在其博客中提到的術(shù)語(yǔ),被使用得要更加頻繁一些,知名度也更高些。而在Spring MVC中,我們稱(chēng)這樣的鍵值對(duì)為矩陣變量。
// GET /owners/42;q=11/pets/21;q=22
@RequestMapping(path = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET)
public void findPet(
@MatrixVariable(name="q", pathVar="ownerId") int q1,
@MatrixVariable(name="q", pathVar="petId") int q2) {
// q1 == 11
// q2 == 22
}
2.8.2 如果要允許矩陣變量的使用,你必須把RequestMappingHandlerMapping類(lèi)的removeSemicolonContent屬性設(shè)置為false。該值默認(rèn)是true的。
MVC的Java編程配置和命名空間配置都提供了啟用矩陣變量的方式。
如果你是使用Java編程的方式,“MVC Java高級(jí)定制化配置”一節(jié)描述了如何對(duì)RequestMappingHandlerMapping進(jìn)行定制。
而使用MVC的命名空間配置時(shí),你可以把元素下的enable-matrix-variables屬性設(shè)置為true。該值默認(rèn)情況下是配置為false的。
2.9 可消費(fèi)的媒體類(lèi)型
你可以指定一組可消費(fèi)的媒體類(lèi)型,縮小映射的范圍。這樣只有當(dāng)請(qǐng)求頭中 Content-Type 的值與指定可消費(fèi)的媒體類(lèi)型中有相同的時(shí)候,請(qǐng)求才會(huì)被匹配。比如下面這個(gè)例子:
@Controller
@RequestMapping(path = "/pets", method = RequestMethod.POST, consumes="application/json")
public void addPet(@RequestBody Pet pet, Model model) {
// 方法實(shí)現(xiàn)省略
}
指定可消費(fèi)媒體類(lèi)型的表達(dá)式中還可以使用否定,比如,可以使用 !text/plain 來(lái)匹配所有請(qǐng)求頭 Content-Type 中不含 text/plain 的請(qǐng)求。
同時(shí),在MediaType類(lèi)中還定義了一些常量,比如APPLICATION_JSON_VALUE、APPLICATION_JSON_UTF8_VALUE等,推薦更多地使用它們。
@Controller
@RequestMapping(path = "/pets", method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
public void addPet(@RequestBody Pet pet, Model model) {
// 方法實(shí)現(xiàn)省略
}
consumes 屬性提供的是方法級(jí)的類(lèi)型支持。與其他屬性不同,當(dāng)在類(lèi)型級(jí)使用時(shí),方法級(jí)的消費(fèi)類(lèi)型將覆蓋類(lèi)型級(jí)的配置,而非繼承關(guān)系。
2.10 可生產(chǎn)的媒體類(lèi)型
你可以指定一組可生產(chǎn)的媒體類(lèi)型,縮小映射的范圍。這樣只有當(dāng)請(qǐng)求頭中 Accept 的值與指定可生產(chǎn)的媒體類(lèi)型中有相同的時(shí)候,請(qǐng)求才會(huì)被匹配。
而且,使用 produces 條件可以確保用于生成響應(yīng)(response)的內(nèi)容與指定的可生產(chǎn)的媒體類(lèi)型是相同的。舉個(gè)例子:
@Controller
@RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
// 方法實(shí)現(xiàn)省略
}
produces 屬性提供的是方法級(jí)的類(lèi)型支持。與其他屬性不同,當(dāng)在類(lèi)型級(jí)使用時(shí),方法級(jí)的消費(fèi)類(lèi)型將覆蓋類(lèi)型級(jí)的配置,而非繼承關(guān)系。
2.11 請(qǐng)求參數(shù)與請(qǐng)求頭的值
你可以篩選請(qǐng)求參數(shù)的條件來(lái)縮小請(qǐng)求匹配范圍,比如"myParam"、"!myParam"及"myParam=myValue"等。前兩個(gè)條件用于篩選存在/不存在某些請(qǐng)求參數(shù)的請(qǐng)求,
第三個(gè)條件篩選具有特定參數(shù)值的請(qǐng)求。下面有個(gè)例子,展示了如何使用請(qǐng)求參數(shù)值的篩選條件:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// 實(shí)際實(shí)現(xiàn)省略
}
}
同樣,你可以用相同的條件來(lái)篩選請(qǐng)求頭的出現(xiàn)與否,或者篩選出一個(gè)具有特定值的請(qǐng)求頭:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping(path = "/pets", method = RequestMethod.GET, headers="myHeader=myValue")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// 方法體實(shí)現(xiàn)省略
}
}
3 定義@RequestMapping注解的處理方法(handler method)
3.1 使用@RequestParam將請(qǐng)求參數(shù)綁定至方法參數(shù)
@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {
// ...
@RequestMapping(method = RequestMapping.GET)
public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ,..
}
若參數(shù)使用了該注解,則該參數(shù)默認(rèn)是必須提供的,但你也可以把該參數(shù)標(biāo)注為非必須的:只需要將@RequestParam注解的required屬性設(shè)置為false即可(比如,@RequestParam(path="id", required=false))。
若所注解的方法參數(shù)類(lèi)型不是String,則類(lèi)型轉(zhuǎn)換會(huì)自動(dòng)地發(fā)生。詳見(jiàn)"方法參數(shù)與類(lèi)型轉(zhuǎn)換"一節(jié)
若@RequestParam注解的參數(shù)類(lèi)型是Map或者M(jìn)ultiValueMap,則該Map中會(huì)自動(dòng)填充所有的請(qǐng)求參數(shù)。
3.2 使用@RequestBody注解映射請(qǐng)求體
方法參數(shù)中的@RequestBody注解暗示了方法參數(shù)應(yīng)該被綁定了HTTP請(qǐng)求體的值。舉個(gè)例子:
@RequestMapping(path = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
writer.write(body);
}
3.3 使用@ResponseBody注解映射響應(yīng)體
@ResponseBody注解與@RequestBody注解類(lèi)似。@ResponseBody注解可被應(yīng)用于方法上,標(biāo)志該方法的返回值應(yīng)該被直接寫(xiě)回到HTTP響應(yīng)體中去(而不會(huì)被被放置到Model中或被解釋為一個(gè)視圖名)。舉個(gè)例子:
@RequestMapping(path = "/something", method = RequestMethod.PUT)
@ResponseBody
public String helloWorld() {
return "Hello World"
}
上面的代碼結(jié)果是文本Hello World將被寫(xiě)入HTTP的響應(yīng)流中。
與@RequestBody注解類(lèi)似,Spring使用了一個(gè)HttpMessageConverter來(lái)將返回對(duì)象轉(zhuǎn)換到響應(yīng)體中。
3.4 使用@RestController注解創(chuàng)建REST控制器
當(dāng)今讓控制器實(shí)現(xiàn)一個(gè)REST API是非常常見(jiàn)的,這種場(chǎng)景下控制器只需要提供JSON、XML或其他自定義的媒體類(lèi)型內(nèi)容即可。
你不需要在每個(gè)@RequestMapping方法上都增加一個(gè)@ResponseBody注解,更簡(jiǎn)明的做法是,給你的控制器加上一個(gè)@RestController的注解。
@RestController是一個(gè)原生內(nèi)置的注解,它結(jié)合了@ResponseBody與@Controller注解的功能。
3.5 對(duì)方法使用@ModelAttribute注解
@ModelAttribute注解可被應(yīng)用在方法或方法參數(shù)上
3.5.1 被注解于方法上時(shí)的用法
注解在方法上的@ModelAttribute說(shuō)明了方法的作用是用于添加一個(gè)或多個(gè)屬性到model上。這樣的方法能接受與@RequestMapping注解相同的參數(shù)類(lèi)型,
只不過(guò)不能直接被映射到具體的請(qǐng)求上。在同一個(gè)控制器中,注解了@ModelAttribute的方法實(shí)際上會(huì)在@RequestMapping方法之前被調(diào)用。以下是幾個(gè)例子:
// Add one attribute
// The return value of the method is added to the model under the name "account"
// You can customize the name via @ModelAttribute("myAccount")
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountManager.findAccount(number);
}
// Add multiple attributes
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountManager.findAccount(number));
// add more ...
}
@ModelAttribute方法通常被用來(lái)填充一些公共需要的屬性或數(shù)據(jù),比如一個(gè)下拉列表所預(yù)設(shè)的幾種狀態(tài),或者寵物的幾種類(lèi)型,或者去取得一個(gè)HTML表單渲染所需要的命令對(duì)象,比如Account等。
留意@ModelAttribute方法的兩種風(fēng)格。在第一種寫(xiě)法中,方法通過(guò)返回值的方式默認(rèn)地將添加一個(gè)屬性;在第二種寫(xiě)法中,方法接收一個(gè)Model對(duì)象,
然后可以向其中添加任意數(shù)量的屬性。你可以在根據(jù)需要,在兩種風(fēng)格中選擇合適的一種。
一個(gè)控制器可以擁有數(shù)量不限的@ModelAttribute方法。同個(gè)控制器內(nèi)的所有這些方法,都會(huì)在@RequestMapping方法之前被調(diào)用。
@ModelAttribute方法也可以定義在@ControllerAdvice注解的類(lèi)中,并且這些@ModelAttribute可以同時(shí)對(duì)許多控制器生效。具體的信息可以參考使用@ControllerAdvice輔助控制器。
屬性名沒(méi)有被顯式指定的時(shí)候又當(dāng)如何呢?在這種情況下,框架將根據(jù)屬性的類(lèi)型給予一個(gè)默認(rèn)名稱(chēng)。舉個(gè)例子,若方法返回一個(gè)Account類(lèi)型的對(duì)象,
則默認(rèn)的屬性名為"account"。你可以通過(guò)設(shè)置@ModelAttribute注解的值來(lái)改變默認(rèn)值。當(dāng)向Model中直接添加屬性時(shí),請(qǐng)使用合適的重載方法addAttribute(..)-即,帶或不帶屬性名的方法。
@ModelAttribute注解也可以被用在@RequestMapping方法上。這種情況下,@RequestMapping方法的返回值將會(huì)被解釋為model的一個(gè)屬性,而非一個(gè)視圖名
3.5.2 在方法參數(shù)上使用@ModelAttribute注解
注解在方法參數(shù)上的@ModelAttribute說(shuō)明了該方法參數(shù)的值將由model中取得。如果model中找不到,那么該參數(shù)會(huì)先被實(shí)例化,然后被添加到model中。
在model中存在以后,請(qǐng)求中所有名稱(chēng)匹配的參數(shù)都會(huì)填充到該參數(shù)中。這在Spring MVC中被稱(chēng)為數(shù)據(jù)綁定,一個(gè)非常有用的特性,節(jié)約了你每次都需要手動(dòng)從表格數(shù)據(jù)中轉(zhuǎn)換這些字段數(shù)據(jù)的時(shí)間。
@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute Pet pet) { }
以上面的代碼為例,這個(gè)Pet類(lèi)型的實(shí)例可能來(lái)自哪里呢?有幾種可能:
它可能因?yàn)锧SessionAttributes注解的使用已經(jīng)存在于model中——詳見(jiàn)"在請(qǐng)求之間使用@SessionAttributes注解,使用HTTP會(huì)話(huà)保存模型數(shù)據(jù)"一節(jié)
它可能因?yàn)樵谕瑐€(gè)控制器中使用了@ModelAttribute方法已經(jīng)存在于model中——正如上一小節(jié)所敘述的
它可能是由URI模板變量和類(lèi)型轉(zhuǎn)換中取得的(下面會(huì)詳細(xì)講解)
它可能是調(diào)用了自身的默認(rèn)構(gòu)造器被實(shí)例化出來(lái)的
@ModelAttribute方法常用于從數(shù)據(jù)庫(kù)中取一個(gè)屬性值,該值可能通過(guò)@SessionAttributes注解在請(qǐng)求中間傳遞。在一些情況下,使用URI模板變量和類(lèi)型轉(zhuǎn)換的方式來(lái)取得一個(gè)屬性是更方便的方式。這里有個(gè)例子:
@RequestMapping(path = "/accounts/{account}", method = RequestMethod.PUT)
public String save(@ModelAttribute("account") Account account) {
}
上面這個(gè)例子中,model屬性的名稱(chēng)("account")與URI模板變量的名稱(chēng)相匹配。如果你配置了一個(gè)可以將String類(lèi)型的賬戶(hù)值轉(zhuǎn)換成Account類(lèi)型實(shí)例的轉(zhuǎn)換器Converter,那么上面這段代碼就可以工作的很好,而不需要再額外寫(xiě)一個(gè)@ModelAttribute方法。
下一步就是數(shù)據(jù)的綁定。WebDataBinder類(lèi)能將請(qǐng)求參數(shù)——包括字符串的查詢(xún)參數(shù)和表單字段等——通過(guò)名稱(chēng)匹配到model的屬性上。
成功匹配的字段在需要的時(shí)候會(huì)進(jìn)行一次類(lèi)型轉(zhuǎn)換(從String類(lèi)型到目標(biāo)字段的類(lèi)型),然后被填充到model對(duì)應(yīng)的屬性中。
如何在控制器層來(lái)定制數(shù)據(jù)綁定的過(guò)程,在這一節(jié) "定制WebDataBinder的初始化"中提及。
進(jìn)行了數(shù)據(jù)綁定后,則可能會(huì)出現(xiàn)一些錯(cuò)誤,比如沒(méi)有提供必須的字段、類(lèi)型轉(zhuǎn)換過(guò)程的錯(cuò)誤等。若想檢查這些錯(cuò)誤,
可以在注解了@ModelAttribute的參數(shù)緊跟著聲明一個(gè)BindingResult參數(shù):
@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
拿到BindingResult參數(shù)后,你可以檢查是否有錯(cuò)誤。有時(shí)你可以通過(guò)Spring的表單標(biāo)簽來(lái)在同一個(gè)表單上顯示錯(cuò)誤信息。
BindingResult被用于記錄數(shù)據(jù)綁定過(guò)程的錯(cuò)誤,因此除了數(shù)據(jù)綁定外,你還可以把該對(duì)象傳給自己定制的驗(yàn)證器來(lái)調(diào)用驗(yàn)證。
這使得數(shù)據(jù)綁定過(guò)程和驗(yàn)證過(guò)程出現(xiàn)的錯(cuò)誤可以被搜集到一處,然后一并返回給用戶(hù):
@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "petForm";
}
// ...
}
又或者,你可以通過(guò)添加一個(gè)JSR-303規(guī)范的@Valid注解,這樣驗(yàn)證器會(huì)自動(dòng)被調(diào)用。
@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
3.6 使用@SessionAttributes注解,使用HTTP會(huì)話(huà)保存模型數(shù)據(jù)
類(lèi)型級(jí)別的@SessionAttributes注解聲明了某個(gè)特定處理器所使用的會(huì)話(huà)屬性。通常它會(huì)列出該類(lèi)型希望存儲(chǔ)到session或converstaion中的model屬性名或model的類(lèi)型名,一般是用于在請(qǐng)求之間保存一些表單數(shù)據(jù)的bean。
以下的代碼段演示了該注解的用法,它指定了模型屬性的名稱(chēng)
@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
3.7 使用"application/x-www-form-urlencoded"數(shù)據(jù)
對(duì)于不是使用的瀏覽器的客戶(hù)端,我們也推薦使用這個(gè)注解來(lái)處理請(qǐng)求。但當(dāng)請(qǐng)求是一個(gè)HTTP PUT方法的請(qǐng)求時(shí),有一個(gè)事情需要注意。
瀏覽器可以通過(guò)HTTP的GET方法或POST方法來(lái)提交表單數(shù)據(jù),非瀏覽器的客戶(hù)端還可以通過(guò)HTTP的PUT方法來(lái)提交表單。這就設(shè)計(jì)是個(gè)挑戰(zhàn),
因?yàn)樵赟ervlet規(guī)范中明確規(guī)定,ServletRequest.getParameter*()系列的方法只能支持通過(guò)HTTP POST方法的方式提交表單,而不支持HTTP PUT的方式。
為了支持HTTP的PUT類(lèi)型和PATCH類(lèi)型的請(qǐng)求,Spring的spring-web模塊提供了一個(gè)過(guò)濾器HttpPutFormContentFilter。你可以在web.xml文件中配置它:
httpPutFormFilter
org.springframework.web.filter.HttpPutFormContentFilter
httpPutFormFilter
dispatcherServlet
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
上面的過(guò)濾器將會(huì)攔截內(nèi)容類(lèi)型(content type)為application/x-www-form-urlencoded、HTTP方法為PUT或PATCH類(lèi)型的請(qǐng)求,然后從請(qǐng)求體中讀取表單數(shù)據(jù),
把它們包裝在ServletRequest中。這是為了使表單數(shù)據(jù)能夠通過(guò)ServletRequest.getParameter*()系列的方法來(lái)拿到。
因?yàn)镠ttpPutFormContentFilter會(huì)消費(fèi)請(qǐng)求體的內(nèi)容,因此,它不應(yīng)該用于處理那些依賴(lài)于其他application/x-www-form-urlencoded轉(zhuǎn)換器的PUT和PATCH請(qǐng)求,
這包括了@RequestBodyMultiValueMap和HttpEntity>。
3.8 使用@CookieValue注解映射cookie值
@CookieValue注解能將一個(gè)方法參數(shù)與一個(gè)HTTP cookie的值進(jìn)行綁定。
看一個(gè)這樣的場(chǎng)景:以下的這個(gè)cookie存儲(chǔ)在一個(gè)HTTP請(qǐng)求中:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
下面的代碼演示了拿到JSESSIONID這個(gè)cookie值的方法:
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {
//...
}
若注解的目標(biāo)方法參數(shù)不是String類(lèi)型,則類(lèi)型轉(zhuǎn)換會(huì)自動(dòng)進(jìn)行。
這個(gè)注解可以注解到處理器方法上,在Servlet環(huán)境和Portlet環(huán)境都能使用。
3.9 使用@RequestHeader注解映射請(qǐng)求頭屬性
@RequestHeader注解能將一個(gè)方法參數(shù)與一個(gè)請(qǐng)求頭屬性進(jìn)行綁定。
以下是一個(gè)請(qǐng)求頭的例子:
Host ? ? ? ? ? ? ? ? ? ?localhost:8080
Accept ? ? ? ? ? ? ? ? ?text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language ? ? ? ? fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding ? ? ? ? gzip,deflate
Accept-Charset ? ? ? ? ?ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive ? ? ? ? ? ? ?300
以下的代碼片段展示了如何取得Accept-Encoding請(qǐng)求頭和Keep-Alive請(qǐng)求頭的值:
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}