Spring MVC 定義@RequestMapping注解的處理方法

2018-07-26 15:36 更新

使用@RequestMapping注解的處理方法可以擁有非常靈活的方法簽名,它支持的方法參數(shù)及返回值類型將在接下來的小節(jié)講述。大多數(shù)參數(shù)都可以任意的次序出現(xiàn),除了唯一的一個例外:BindingResult參數(shù)。這在下節(jié)也會詳細描述。

Spring 3.1中新增了一些類,用以增強注解了@RequestMapping的處理方法,分別是RequestMappingHandlerMapping類和RequestMappingHandlerAdapter類。我們鼓勵使用這組新的類,如果要使用Spring 3.1及以后版本的新特性,這組類甚至是必須使用的。這些增強類在MVC的命名空間配置和MVC的Java編程方式配置中都是默認(rèn)開啟的,如果不是使用這兩種方法,那么就需要顯式地配置。

支持的方法參數(shù)類型

下面列出所有支持的方法參數(shù)類型:

  • 請求或響應(yīng)對象(Servlet API)??梢允侨魏尉唧w的請求或響應(yīng)類型的對象,比如,ServletRequestHttpServletRequest對象等。
  • HttpSession類型的會話對象(Servlet API)。使用該類型的參數(shù)將要求這樣一個session的存在,因此這樣的參數(shù)永不為null。

存取session可能不是線程安全的,特別是在一個Servlet的運行環(huán)境中。如果應(yīng)用可能有多個請求同時并發(fā)存取一個session場景,請考慮將RequestMappingHandlerAdapter類中的"synchronizeOnSession"標(biāo)志設(shè)置為"true"。

  • org.springframework.web.context.request.WebRequestorg.springframework.web.context.request.NativeWebRequest。允許存取一般的請求參數(shù)和請求/會話范圍的屬性(attribute),同時無需綁定使用Servlet/Portlet的API
  • 當(dāng)前請求的地區(qū)信息java.util.Locale,由已配置的最相關(guān)的地區(qū)解析器解析得到。在MVC的環(huán)境下,就是應(yīng)用中配置的LocaleResolverLocaleContextResolver
  • 與當(dāng)前請求綁定的時區(qū)信息java.util.TimeZone(java 6以上的版本)/java.time.ZoneId(java 8),由LocaleContextResolver解析得到
  • 用于存取請求正文的java.io.InputStreamjava.io.Reader。該對象與通過Servlet API拿到的輸入流/Reader是一樣的
  • 用于生成響應(yīng)正文的java.io.OutputStreamjava.io.Writer。該對象與通過Servlet API拿到的輸出流/Writer是一樣的
  • org.springframework.http.HttpMethod??梢阅玫紿TTP請求方法
  • 包裝了當(dāng)前被認(rèn)證用戶信息的java.security.Principal
  • @PathVariable注解的方法參數(shù),其存放了URI模板變量中的值。詳見“URI模板變量”一節(jié)
  • @MatrixVariable注解的方法參數(shù),其存放了URI路徑段中的鍵值對。詳見“矩陣變量”一節(jié)
  • @RequestParam注解的方法參數(shù),其存放了Servlet請求中所指定的參數(shù)。參數(shù)的值會被轉(zhuǎn)換成方法參數(shù)所聲明的類型。詳見“使用@RequestParam注解綁定請求參數(shù)至方法參數(shù)”一節(jié)
  • @RequestHeader注解的方法參數(shù),其存放了Servlet請求中所指定的HTTP請求頭的值。參數(shù)的值會被轉(zhuǎn)換成方法參數(shù)所聲明的類型。詳見“使用@RequestHeader注解映射請求頭屬性”一節(jié).
  • @RequestBody注解的參數(shù),提供了對HTTP請求體的存取。參數(shù)的值通過HttpMessageConverter被轉(zhuǎn)換成方法參數(shù)所聲明的類型。詳見“使用@RequestBody注解映射請求體”一節(jié)"
  • @RequestPart注解的參數(shù),提供了對一個"multipart/form-data請求塊(request part)內(nèi)容的存取。更多的信息請參考21.10.5 “處理客戶端文件上傳的請求”一節(jié)21.10 “Spring對多部分文件上傳的支持”一節(jié)
  • HttpEntity<?>類型的參數(shù),其提供了對HTTP請求頭和請求內(nèi)容的存取。請求流是通過HttpMessageConverter被轉(zhuǎn)換成entity對象的。詳見“HttpEntity”一節(jié)
  • java.util.Map/org.springframework.io.Model/org.springframework.ui.ModelMap類型的參數(shù),用以增強默認(rèn)暴露給視圖層的模型(model)的功能
  • org.springframework.web.servlet.mvc.support.RedirectAttributes類型的參數(shù),用以指定重定向下要使用到的屬性集以及添加flash屬性(暫存在服務(wù)端的屬性,它們會在下次重定向請求的范圍中有效)。詳見“向重定向請求傳遞參數(shù)”一節(jié)
  • 命令或表單對象,它們用于將請求參數(shù)直接綁定到bean字段(可能是通過setter方法)。你可以通過@InitBinder注解和/或HanderAdapter的配置來定制這個過程的類型轉(zhuǎn)換。具體請參考RequestMappingHandlerAdapterwebBindingInitializer屬性的文檔。這樣的命令對象,以及其上的驗證結(jié)果,默認(rèn)會被添加到模型model中,鍵名默認(rèn)是該命令對象類的類名——比如,some.package.OrderAddress類型的命令對象就使用屬性名orderAddress類獲取。ModelAttribute注解可以應(yīng)用在方法參數(shù)上,用以指定該模型所用的屬性名
  • org.springframework.validation.Errors / org.springframework.validation.BindingResult驗證結(jié)果對象,用于存儲前面的命令或表單對象的驗證結(jié)果(緊接其前的第一個方法參數(shù))。
  • org.springframework.web.bind.support.SessionStatus對象,用以標(biāo)記當(dāng)前的表單處理已結(jié)束。這將觸發(fā)一些清理操作:@SessionAttributes在類級別注解的屬性將被移除
  • org.springframework.web.util.UriComponentsBuilder構(gòu)造器對象,用于構(gòu)造當(dāng)前請求URL相關(guān)的信息,比如主機名、端口號、資源類型(scheme)、上下文路徑、servlet映射中的相對部分(literal part)等

在參數(shù)列表中,ErrorsBindingResult參數(shù)必須緊跟在其所綁定的驗證對象后面。這是因為,在參數(shù)列表中允許有多于一個的模型對象,Spring會為它們創(chuàng)建不同的BindingResult實例。因此,下面這樣的代碼是不能工作的:

BindingResult與@ModelAttribute錯誤的參數(shù)次序

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }

上例中,因為在模型對象Pet和驗證結(jié)果對象BindingResult中間還插了一個Model參數(shù),這是不行的。要達到預(yù)期的效果,必須調(diào)整一下參數(shù)的次序:

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }

對于一些帶有required屬性的注解(比如@RequestParam、@RequestHeader等),JDK 1.8的java.util.Optional可以作為被它們注解的方法參數(shù)。在這種情況下,使用java.util.Optionalrequired=false的作用是相同的。

支持的方法返回類型

以下是handler方法允許的所有返回類型:

  • ModelAndView對象,其中model隱含填充了命令對象,以及注解了@ModelAttribute字段的存取器被調(diào)用所返回的值。
  • Model對象,其中視圖名稱默認(rèn)由RequestToViewNameTranslator決定,model隱含填充了命令對象以及注解了@ModelAttribute字段的存取器被調(diào)用所返回的值
  • Map對象,用于暴露model,其中視圖名稱默認(rèn)由RequestToViewNameTranslator決定,model隱含填充了命令對象以及注解了@ModelAttribute字段的存取器被調(diào)用所返回的值
  • View對象。其中model隱含填充了命令對象,以及注解了@ModelAttribute字段的存取器被調(diào)用所返回的值。handler方法也可以增加一個Model類型的方法參數(shù)來增強model
  • String對象,其值會被解析成一個邏輯視圖名。其中,model將默認(rèn)填充了命令對象以及注解了@ModelAttribute字段的存取器被調(diào)用所返回的值。handler方法也可以增加一個Model類型的方法參數(shù)來增強model
  • void。如果處理器方法中已經(jīng)對response響應(yīng)數(shù)據(jù)進行了處理(比如在方法參數(shù)中定義一個ServletResponseHttpServletResponse類型的參數(shù)并直接向其響應(yīng)體中寫東西),那么方法可以返回void。handler方法也可以增加一個Model類型的方法參數(shù)來增強model
  • 如果處理器方法注解了ResponseBody,那么返回類型將被寫到HTTP的響應(yīng)體中,而返回值會被HttpMessageConverters轉(zhuǎn)換成所方法聲明的參數(shù)類型。詳見使用"@ResponseBody注解映射響應(yīng)體"一節(jié)
  • HttpEntity<?>ResponseEntity<?>對象,用于提供對Servlet HTTP響應(yīng)頭和響應(yīng)內(nèi)容的存取。對象體會被HttpMessageConverters轉(zhuǎn)換成響應(yīng)流。詳見使用HttpEntity一節(jié)
  • HttpHeaders對象,返回一個不含響應(yīng)體的response
  • Callable<?>對象。當(dāng)應(yīng)用希望異步地返回方法值時使用,這個過程由Spring MVC自身的線程來管理
  • DeferredResult<?>對象。當(dāng)應(yīng)用希望方法的返回值交由線程自身決定時使用
  • ListenableFuture<?>對象。當(dāng)應(yīng)用希望方法的返回值交由線程自身決定時使用
  • ResponseBodyEmitter對象,可用它異步地向響應(yīng)體中同時寫多個對象,also supported as the body within a ResponseEntity
  • SseEmitter對象,可用它異步地向響應(yīng)體中寫服務(wù)器端事件(Server-Sent Events),also supported as the body within a ResponseEntity
  • StreamingResponseBody對象,可用它異步地向響應(yīng)對象的輸出流中寫東西。also supported as the body within a ResponseEntity
  • 其他任何返回類型,都會被處理成model的一個屬性并返回給視圖,該屬性的名稱為方法級的@ModelAttribute所注解的字段名(或者以返回類型的類名作為默認(rèn)的屬性名)。model隱含填充了命令對象以及注解了@ModelAttribute字段的存取器被調(diào)用所返回的值

使用@RequestParam將請求參數(shù)綁定至方法參數(shù)

你可以使用@RequestParam注解將請求參數(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ù)類型不是String,則類型轉(zhuǎn)換會自動地發(fā)生。詳見"方法參數(shù)與類型轉(zhuǎn)換"一節(jié)

@RequestParam注解的參數(shù)類型是Map<String, String>或者MultiValueMap<String, String>,則該Map中會自動填充所有的請求參數(shù)。

使用@RequestBody注解映射請求體

方法參數(shù)中的@RequestBody注解暗示了方法參數(shù)應(yīng)該被綁定了HTTP請求體的值。舉個例子:

@RequestMapping(path = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
    writer.write(body);
}

請求體到方法參數(shù)的轉(zhuǎn)換是由HttpMessageConverter完成的。HttpMessageConverter負(fù)責(zé)將HTTP請求信息轉(zhuǎn)換成對象,以及將對象轉(zhuǎn)換回一個HTTP響應(yīng)體。對于@RequestBody注解,RequestMappingHandlerAdapter提供了以下幾種默認(rèn)的HttpMessageConverter支持:

  • ByteArrayHttpMessageConverter用以轉(zhuǎn)換字節(jié)數(shù)組
  • StringHttpMessageConverter用以轉(zhuǎn)換字符串
  • FormHttpMessageConverter用以將表格數(shù)據(jù)轉(zhuǎn)換成MultiValueMap<String, String>或從MultiValueMap<String, String>中轉(zhuǎn)換出表格數(shù)據(jù)
  • SourceHttpMessageConverter用于javax.xml.transform.Source類的互相轉(zhuǎn)換

關(guān)于這些轉(zhuǎn)換器的更多信息,請參考"HTTP信息轉(zhuǎn)換器"一節(jié)。另外,如果使用的是MVC命名空間或Java編程的配置方式,會有更多默認(rèn)注冊的消息轉(zhuǎn)換器。更多信息,請參考"啟用MVC Java編程配置或MVC XML命令空間配置"一節(jié)。

若你更傾向于閱讀和編寫XML文件,那么你需要配置一個MarshallingHttpMessageConverter并為其提供org.springframework.oxm包下的一個MarshallerUnmarshaller實現(xiàn)。下面的示例就為你展示如何直接在配置文件中配置它。但如果你的應(yīng)用是使用MVC命令空間或MVC Java編程的方式進行配置的,則請參考"啟用MVC Java編程配置或MVC XML命令空間配置"這一節(jié)。

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <util:list id="beanList">
            <ref bean="stringHttpMessageConverter"/>
            <ref bean="marshallingHttpMessageConverter"/>
        </util:list>
    </property
</bean>

<bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter"/>

<bean id="marshallingHttpMessageConverter"
        class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
    <property name="marshaller" ref="castorMarshaller"/>
    <property name="unmarshaller" ref="castorMarshaller"/>
</bean>

<bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>

注解了@RequestBody的方法參數(shù)還可以被@Valid注解,這樣框架會使用已配置的Validator實例來對該參數(shù)進行驗證。若你的應(yīng)用是使用MVC命令空間或MVC Java編程的方式配置的,框架會假設(shè)在classpath路徑下存在一個符合JSR-303規(guī)范的驗證器,并自動將其作為默認(rèn)配置。

@ModelAttribute注解的參數(shù)一樣,Errors也可以被傳入為方法參數(shù),用于檢查錯誤。如果沒有聲明這樣一個參數(shù),那么程序會拋出一個MethodArgumentNotValidException異常。該異常默認(rèn)由DefaultHandlerExceptionResolver處理,處理程序會返回一個400錯誤給客戶端。

關(guān)于如何通過MVC命令空間或MVC Java編程的方式配置消息轉(zhuǎn)換器和驗證器,也請參考"啟用MVC Java編程配置或MVC XML命令空間配置"一節(jié)

使用@ResponseBody注解映射響應(yīng)體

@ResponseBody注解與@RequestBody注解類似。@ResponseBody注解可被應(yīng)用于方法上,標(biāo)志該方法的返回值(更正,原文是return type,看起來應(yīng)該是返回值)應(yīng)該被直接寫回到HTTP響應(yīng)體中去(而不會被被放置到Model中或被解釋為一個視圖名)。舉個例子:

@RequestMapping(path = "/something", method = RequestMethod.PUT)
@ResponseBody
public String helloWorld() {
    return "Hello World"
}

上面的代碼結(jié)果是文本Hello World將被寫入HTTP的響應(yīng)流中。

@RequestBody注解類似,Spring使用了一個HttpMessageConverter來將返回對象轉(zhuǎn)換到響應(yīng)體中。關(guān)于這些轉(zhuǎn)換器的更多信息,請參考"HTTP信息轉(zhuǎn)換器"一節(jié)

使用@RestController注解創(chuàng)建REST控制器

當(dāng)今讓控制器實現(xiàn)一個REST API是非常常見的,這種場景下控制器只需要提供JSON、XML或其他自定義的媒體類型內(nèi)容即可。你不需要在每個@RequestMapping方法上都增加一個@ResponseBody注解,更簡明的做法是,給你的控制器加上一個@RestController的注解。

@RestController是一個原生內(nèi)置的注解,它結(jié)合了@ResponseBody@Controller注解的功能。不僅如此,它也讓你的控制器更表義,而且在框架未來的發(fā)布版本中,它也可能承載更多的意義。

與普通的@Controller無異,@RestController也可以與@ControllerAdvicebean配合使用。更多細節(jié),請見使用@ControllerAdvice輔助控制器。

使用HTTP實體HttpEntity

HttpEntity@RequestBody@ResponseBody很相似。除了能獲得請求體和響應(yīng)體中的內(nèi)容之外,HttpEntity(以及專門負(fù)責(zé)處理響應(yīng)的ResponseEntity子類)還可以存取請求頭和響應(yīng)頭,像下面這樣:

@RequestMapping("/something")
public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
    String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader");
    byte[] requestBody = requestEntity.getBody();

    // do something with request header and body

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("MyResponseHeader", "MyValue");
    return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}

上面這段示例代碼先是獲取了MyRequestHeader請求頭的值,然后讀取請求體的主體內(nèi)容。讀完以后往影響頭中添加了一個自己的響應(yīng)頭MyResponseHeader,然后向響應(yīng)流中寫了字符串Hello World,最后把響應(yīng)狀態(tài)碼設(shè)置為201(創(chuàng)建成功)。

@RequestBody@ResponseBody注解一樣,Spring使用了HttpMessageConverter來對請求流和響應(yīng)流進行轉(zhuǎn)換。關(guān)于這些轉(zhuǎn)換器的更多信息,請閱讀上一小節(jié)以及"HTTP信息轉(zhuǎn)換器"這一節(jié)。

對方法使用@ModelAttribute注解

@ModelAttribute注解可被應(yīng)用在方法或方法參數(shù)上。本節(jié)將介紹其被注解于方法上時的用法,下節(jié)會介紹其被用于注解方法參數(shù)的用法。

注解在方法上的@ModelAttribute說明了方法的作用是用于添加一個或多個屬性到model上。這樣的方法能接受與@RequestMapping注解相同的參數(shù)類型,只不過不能直接被映射到具體的請求上。在同一個控制器中,注解了@ModelAttribute的方法實際上會在@RequestMapping方法之前被調(diào)用。以下是幾個例子:

// 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方法通常被用來填充一些公共需要的屬性或數(shù)據(jù),比如一個下拉列表所預(yù)設(shè)的幾種狀態(tài),或者寵物的幾種類型,或者去取得一個HTML表單渲染所需要的命令對象,比如Account等。

留意@ModelAttribute方法的兩種風(fēng)格。在第一種寫法中,方法通過返回值的方式默認(rèn)地將添加一個屬性;在第二種寫法中,方法接收一個Model對象,然后可以向其中添加任意數(shù)量的屬性。你可以在根據(jù)需要,在兩種風(fēng)格中選擇合適的一種。

一個控制器可以擁有數(shù)量不限的@ModelAttribute方法。同個控制器內(nèi)的所有這些方法,都會在@RequestMapping方法之前被調(diào)用。

@ModelAttribute方法也可以定義在@ControllerAdvice注解的類中,并且這些@ModelAttribute可以同時對許多控制器生效。具體的信息可以參考使用@ControllerAdvice輔助控制器

屬性名沒有被顯式指定的時候又當(dāng)如何呢?在這種情況下,框架將根據(jù)屬性的類型給予一個默認(rèn)名稱。舉個例子,若方法返回一個Account類型的對象,則默認(rèn)的屬性名為"account"。你可以通過設(shè)置@ModelAttribute注解的值來改變默認(rèn)值。當(dāng)向Model中直接添加屬性時,請使用合適的重載方法addAttribute(..)-即,帶或不帶屬性名的方法。

@ModelAttribute注解也可以被用在@RequestMapping方法上。這種情況下,@RequestMapping方法的返回值將會被解釋為model的一個屬性,而非一個視圖名。此時視圖名將以視圖命名約定來方式來決議,與返回值為void的方法所采用的處理方法類似——請見視圖:請求與視圖名的對應(yīng)。

在方法參數(shù)上使用@ModelAttribute注解

如上一小節(jié)所解釋,@ModelAttribute注解既可以被用在方法上,也可以被用在方法參數(shù)上。這一小節(jié)將介紹它注解在方法參數(shù)上時的用法。

注解在方法參數(shù)上的@ModelAttribute說明了該方法參數(shù)的值將由model中取得。如果model中找不到,那么該參數(shù)會先被實例化,然后被添加到model中。在model中存在以后,請求中所有名稱匹配的參數(shù)都會填充到該參數(shù)中。這在Spring MVC中被稱為數(shù)據(jù)綁定,一個非常有用的特性,節(jié)約了你每次都需要手動從表格數(shù)據(jù)中轉(zhuǎn)換這些字段數(shù)據(jù)的時間。

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute Pet pet) { }

以上面的代碼為例,這個Pet類型的實例可能來自哪里呢?有幾種可能:

@ModelAttribute方法常用于從數(shù)據(jù)庫中取一個屬性值,該值可能通過@SessionAttributes注解在請求中間傳遞。在一些情況下,使用URI模板變量和類型轉(zhuǎn)換的方式來取得一個屬性是更方便的方式。這里有個例子:

@RequestMapping(path = "/accounts/{account}", method = RequestMethod.PUT)
public String save(@ModelAttribute("account") Account account) {

}

上面這個例子中,model屬性的名稱("account")與URI模板變量的名稱相匹配。如果你配置了一個可以將String類型的賬戶值轉(zhuǎn)換成Account類型實例的轉(zhuǎn)換器Converter<String, Account>,那么上面這段代碼就可以工作的很好,而不需要再額外寫一個@ModelAttribute方法。

下一步就是數(shù)據(jù)的綁定。WebDataBinder類能將請求參數(shù)——包括字符串的查詢參數(shù)和表單字段等——通過名稱匹配到model的屬性上。成功匹配的字段在需要的時候會進行一次類型轉(zhuǎn)換(從String類型到目標(biāo)字段的類型),然后被填充到model對應(yīng)的屬性中。數(shù)據(jù)綁定和數(shù)據(jù)驗證的問題在第8章 驗證,數(shù)據(jù)綁定和類型轉(zhuǎn)換中提到。如何在控制器層來定制數(shù)據(jù)綁定的過程,在這一節(jié) "定制WebDataBinder的初始化"中提及。

進行了數(shù)據(jù)綁定后,則可能會出現(xiàn)一些錯誤,比如沒有提供必須的字段、類型轉(zhuǎn)換過程的錯誤等。若想檢查這些錯誤,可以在注解了@ModelAttribute的參數(shù)緊跟著聲明一個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ù)后,你可以檢查是否有錯誤。有時你可以通過Spring的<errors>表單標(biāo)簽來在同一個表單上顯示錯誤信息。

BindingResult被用于記錄數(shù)據(jù)綁定過程的錯誤,因此除了數(shù)據(jù)綁定外,你還可以把該對象傳給自己定制的驗證器來調(diào)用驗證。這使得數(shù)據(jù)綁定過程和驗證過程出現(xiàn)的錯誤可以被搜集到一處,然后一并返回給用戶:

@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";
    }

    // ...

}

又或者,你可以通過添加一個JSR-303規(guī)范的@Valid注解,這樣驗證器會自動被調(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";
    }

    // ...

}

關(guān)于如何配置并使用驗證,可以參考第8.8小節(jié) "Spring驗證"第8章 驗證,數(shù)據(jù)綁定和類型轉(zhuǎn)換。

在請求之間使用@SessionAttributes注解,使用HTTP會話保存模型數(shù)據(jù)

類型級別的@SessionAttributes注解聲明了某個特定處理器所使用的會話屬性。通常它會列出該類型希望存儲到session或converstaion中的model屬性名或model的類型名,一般是用于在請求之間保存一些表單數(shù)據(jù)的bean。

以下的代碼段演示了該注解的用法,它指定了模型屬性的名稱

@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
    // ...
}

使用"application/x-www-form-urlencoded"數(shù)據(jù)

上一小節(jié)講述了如何使用@ModelAttribute支持客戶端瀏覽器的多次表單提交請求。對于不是使用的瀏覽器的客戶端,我們也推薦使用這個注解來處理請求。但當(dāng)請求是一個HTTP PUT方法的請求時,有一個事情需要注意。瀏覽器可以通過HTTP的GET方法或POST方法來提交表單數(shù)據(jù),非瀏覽器的客戶端還可以通過HTTP的PUT方法來提交表單。這就設(shè)計是個挑戰(zhàn),因為在Servlet規(guī)范中明確規(guī)定,ServletRequest.getParameter*()系列的方法只能支持通過HTTP POST方法的方式提交表單,而不支持HTTP PUT的方式。

為了支持HTTP的PUT類型和PATCH類型的請求,Spring的spring-web模塊提供了一個過濾器HttpPutFormContentFilter。你可以在web.xml文件中配置它:

    <filter>
        <filter-name>httpPutFormFilter</filter-name>
        <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>httpPutFormFilter</filter-name>
        <servlet-name>dispatcherServlet</servlet-name>
    </filter-mapping>

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>

上面的過濾器將會攔截內(nèi)容類型(content type)為application/x-www-form-urlencoded、HTTP方法為PUT或PATCH類型的請求,然后從請求體中讀取表單數(shù)據(jù),把它們包裝在ServletRequest中。這是為了使表單數(shù)據(jù)能夠通過ServletRequest.getParameter*()系列的方法來拿到。

因為HttpPutFormContentFilter會消費請求體的內(nèi)容,因此,它不應(yīng)該用于處理那些依賴于其他application/x-www-form-urlencoded轉(zhuǎn)換器的PUT和PATCH請求,這包括了@RequestBodyMultiValueMap<String, String>HttpEntity<MultiValueMap<String, String>>。

使用@CookieValue注解映射cookie值

@CookieValue注解能將一個方法參數(shù)與一個HTTP cookie的值進行綁定。

看一個這樣的場景:以下的這個cookie存儲在一個HTTP請求中:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

下面的代碼演示了拿到JSESSIONID這個cookie值的方法:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {
    //...
}

若注解的目標(biāo)方法參數(shù)不是String類型,則類型轉(zhuǎn)換會自動進行。詳見"方法參數(shù)與類型轉(zhuǎn)換"一節(jié)。

這個注解可以注解到處理器方法上,在Servlet環(huán)境和Portlet環(huán)境都能使用。

使用@RequestHeader注解映射請求頭屬性

@RequestHeader注解能將一個方法參數(shù)與一個請求頭屬性進行綁定。

以下是一個請求頭的例子:

    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請求頭和Keep-Alive請求頭的值:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
        @RequestHeader("Keep-Alive") long keepAlive) {
    //...
}

若注解的目標(biāo)方法參數(shù)不是String類型,則類型轉(zhuǎn)換會自動進行。"方法參數(shù)與類型轉(zhuǎn)換"一節(jié)。

如果@RequestHeader注解應(yīng)用在Map<String, String>、MultiValueMap<String, String>HttpHeaders類型的參數(shù)上,那么所有的請求頭屬性值都會被填充到map中。

Spring內(nèi)置支持將一個逗號分隔的字符串(或其他類型轉(zhuǎn)換系統(tǒng)所能識別的類型)轉(zhuǎn)換成一個String類型的列表/集合。舉個例子,一個注解了@RequestHeader("Accept")的方法參數(shù)可以是一個String類型,但也可以是String[]List<String>類型的。

這個注解可以注解到處理器方法上,在Servlet環(huán)境和Portlet環(huán)境都能使用。

方法參數(shù)與類型轉(zhuǎn)換

從請求參數(shù)、路徑變量、請求頭屬性或者cookie中抽取出來的String類型的值,可能需要被轉(zhuǎn)換成其所綁定的目標(biāo)方法參數(shù)或字段的類型(比如,通過@ModelAttribute將請求參數(shù)綁定到方法參數(shù)上)。如果目標(biāo)類型不是String,Spring會自動進行類型轉(zhuǎn)換。所有的簡單類型諸如int、long、Date都有內(nèi)置的支持。如果想進一步定制這個轉(zhuǎn)換過程,你可以通過WebDataBinder(詳見"定制WebDataBinder的初始化"一節(jié)),或者為Formatters配置一個FormattingConversionService(詳見8.6節(jié) "Spring字段格式化"一節(jié))來做到。

定制WebDataBinder的初始化

如果想通過Spring的WebDataBinder在屬性編輯器中做請求參數(shù)的綁定,你可以使用在控制器內(nèi)使用@InitBinder注解的方法、在注解了@ControllerAdvice的類中使用@InitBinder注解的方法,或者提供一個定制的WebBindingInitializer。更多的細節(jié),請參考使用@ControllerAdvice輔助控制器一節(jié)。

數(shù)據(jù)綁定的定制:使用@InitBinder

使用@InitBinder注解控制器的方法,你可以直接在你的控制器類中定制應(yīng)用的數(shù)據(jù)綁定。@InitBinder用來標(biāo)記一些方法,這些方法會初始化一個WebDataBinder并用以為處理器方法填充命令對象和表單對象的參數(shù)。

除了命令/表單對象以及相應(yīng)的驗證結(jié)果對象,這樣的“綁定器初始化”方法能夠接收@RequestMapping所支持的所有參數(shù)類型。“綁定器初始化”方法不能有返回值,因此,一般將它們聲明為void返回類型。特別地,當(dāng)WebDataBinderWebRequestjava.util.Locale一起作為方法參數(shù)時,你可以在代碼中注冊上下文相關(guān)的編輯器。

下面的代碼示例演示了如何使用@InitBinder來配置一個CustomerDateEditor,后者會對所有java.util.Date類型的表單字段進行操作:

@Controller
public class MyFormController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}

或者,你可以使用Spring 4.2提供的addCustomFormatter來指定Formatter的實現(xiàn),而非通過PropertyEditor實例。這在你擁有一個需要Formatter的setup方法,并且該方法位于一個共享的FormattingConversionService中時非常有用。這樣對于控制器級別的綁定規(guī)則的定制,代碼更容易被復(fù)用。

@Controller
public class MyFormController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
    }

    // ...
}

配置定制的WebBindingInitializer

為了externalize數(shù)據(jù)綁定的初始化過程,你可以為WebBindingInitializer接口提供一個自己的實現(xiàn),在其中你可以為AnnotationMethodHandlerAdapter提供一個默認(rèn)的配置bean,以此來覆寫默認(rèn)的配置。

以下的代碼來自PetClinic的應(yīng)用,它展示了為WebBindingInitializer接口提供一個自定義實現(xiàn):org.springframework.samples.petclinic.web.ClinicBindingInitializer完整的配置過程。后者中配置了PetClinic應(yīng)用中許多控制器所需要的屬性編輯器PropertyEditors。

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="cacheSeconds" value="0"/>
    <property name="webBindingInitializer">
        <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer"/>
    </property>
</bean>

@InitBinder方法也可以定義在@ControllerAdvice注解的類上,這樣配置可以為許多控制器所共享。這提供了除使用WebBindingInitializer外的另外一種方法。更多細節(jié)請參考使用@ControllerAdvice輔助控制器一節(jié)。

使用@ControllerAdvice輔助控制器

@ControllerAdvice是一個組件注解,它使得其實現(xiàn)類能夠被classpath掃描自動發(fā)現(xiàn)。若應(yīng)用是通過MVC命令空間或MVC Java編程方式配置,那么該特性默認(rèn)是自動開啟的。

注解@ControllerAdvice的類可以擁有@ExceptionHandler、@InitBinder@ModelAttribute注解的方法,并且這些方法會被應(yīng)用至控制器類層次??的所有@RequestMapping方法上。

你也可以通過@ControllerAdvice的屬性來指定其只對一個子集的控制器生效:

// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class BasePackageAdvice {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class AssignableTypesAdvice {}

更多的細節(jié),請查閱@ControllerAdvice的文檔。

下面兩節(jié),還看不太懂,待譯。

Jackson Serialization View Support

It can sometimes be useful to filter contextually the object that will be serialized to the HTTP response body. In order to provide such capability, Spring MVC has built-in support for rendering with Jackson's Serialization Views.

To use it with an @ResponseBody controller method or controller methods that return ResponseEntity, simply add the @JsonView annotation with a class argument specifying the view class or interface to be used:

_@RestController_
public class UserController {

    _@RequestMapping(path = "/user", method = RequestMethod.GET)_
    _@JsonView(User.WithoutPasswordView.class)_
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    _@JsonView(WithoutPasswordView.class)_
    public String getUsername() {
        return this.username;
    }

    _@JsonView(WithPasswordView.class)_
    public String getPassword() {
        return this.password;
    }
}
\[Note\]Note

Note that despite @JsonView allowing for more than one class to be specified, the use on a controller method is only supported with exactly one class argument. Consider the use of a composite interface if you need to enable multiple views.

For controllers relying on view resolution, simply add the serialization view class to the model:

_@Controller_
public class UserController extends AbstractController {

    _@RequestMapping(path = "/user", method = RequestMethod.GET)_
    public String getUser(Model model) {
        model.addAttribute("user", new User("eric", "7!jd#h23"));
        model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
        return "userView";
    }
}

Jackson JSONP Support

In order to enable JSONP support for @ResponseBody and ResponseEntity methods, declare an @ControllerAdvice bean that extends AbstractJsonpResponseBodyAdvice as shown below where the constructor argument indicates the JSONP query parameter name(s):

_@ControllerAdvice_
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

    public JsonpAdvice() {
        super("callback");
    }
}

For controllers relying on view resolution, JSONP is automatically enabled when the request has a query parameter named jsonp or callback. Those names can be customized through jsonpParameterNamesproperty.


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號