1. 程式人生 > 實用技巧 >Spring Web MVC 中

Spring Web MVC 中

一、過濾器(Filters)

spring web模組提供了一些有用的過濾器。

Form Data

瀏覽器只能通過HTTP GET或HTTP POST提交表單資料,而非瀏覽器客戶端也可以使用HTTP PUT、PATCH和DELETE。Servlet API的ServletRequest.getParameter*()方法只支援HTTP POST的表單欄位訪問。

spring-web模組提供FormContentFilter來攔截內容型別為application/x-www-form-urlencoded的HTTP PUT、PATCH和DELETE請求,從請求主體讀取表單資料,幷包裝ServletRequest,使資料可以通過ServletRequest.getParameter*()來獲取。

Forwarded Headers

當請求通過代理(如負載平衡器)時,主機、埠和方案可能會發生變化,這使得從客戶端角度建立指向正確主機、埠和方案的連結成為一項挑戰。

RFC7239定義了代理可以用來提供原始請求資訊的轉發HTTP頭。還有其他非標準的報頭,包括X-Forwarded-Host、X-Forwarded-Port、X-Forwarded-Proto、X-Forwarded-Ssl和X-Forwarded-Prefix。

ForwardedHeaderFilter是一個Servlet過濾器,它根據轉發的頭修改請求的主機、埠和方案,然後刪除這些頭。

由於應用程式無法知道這些頭是由代理新增的,還是由惡意客戶端新增的,所以轉發的頭需要考慮安全性。這就是為什麼在信任邊界處的代理應該配置為刪除來自外部的不受信任的轉發頭。你還可以將ForwardedHeaderFilter配置為removeOnly=true,在這種情況下,它將刪除但不使用標頭。

Shallow ETag

ShallowEtagHeaderFilter過濾器通過快取寫入響應的內容並從中計算MD5雜湊來建立“shallow”ETag。下一次客戶端傳送時,它會執行相同的操作,但它還會將計算的值與If-None-Match請求頭進行比較,如果兩者相等,則返回304(NOT_MODIFIED)。

此策略節省了網路頻寬,但不節省CPU,因為必須為每個請求計算完整的響應。

此過濾器有一個writewaketag引數,該引數將過濾器配置為寫入弱ETags,類似:W/"02a2d595e6ed9a0b24f027f2b63b134d6"。

CORS

spring mvc通過控制器上的註解為CORS配置提供細粒度的支援。但是,當與spring security一起使用時,我們建議使用內建的CorsFilter,它必須在spring security的過濾器鏈之前呼叫。

二、基於註解的controller

你可以使用Servlet的WebApplicationContext中的標準spring bean定義來定義控制器bean。@Controller型別允許自動檢測,與Spring對在類路徑中檢測@Component類並自動註冊它們的bean定義的支援一致。它是web元件的重要角色。

要啟用@Controller bean的自動檢測,可以將元件掃描新增到Java配置中,如下例所示:

@Configuration
@ComponentScan("org.example.web")
public class WebConfig {

    // ...
}

以下示例顯示了與前一個示例等效的XML配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example.web"/>

    <!-- ... -->

</beans>

@RestController是一個組合註解,它本身是用@Controller和@ResponseBody進行元註解的,指示其每個方法都繼承型別級別@ResponseBody註解的控制器,因此直接寫入響應體,而不是使用HTML模板進行檢視解析和呈現。

AOP代理

在某些情況下,你可能需要在執行時用AOP代理來裝飾控制器。一個例子是如果你選擇在控制器上直接使用@Transactional註釋。在這種情況下,特別是對於控制器,我們建議使用基於類的代理。這通常是控制器的預設選擇。但是,如果控制器必須實現一個不是Spring上下文回撥的介面(例如initializengbean、*Aware等),那麼你可能需要顯式地配置基於類的代理。例如,使用<tx:annotation-driven/>,可以更改為<tx:annotation-driven proxy target class=“true”/>。

請求對映(Request Mapping)

可以使用@RequestMapping註釋將請求對映到控制器方法。它有各種屬性,可以通過URL、HTTP方法、請求引數、頭資訊和媒體型別進行匹配。可以在類級別使用它來表示共享對映,也可以在方法級別使用它來縮小到特定的端點對映。

Spring MVC在方法級別上提供了幾種快捷的方式來替代@RequestMapping:

  • @GetMapping:
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping
@RestController
@RequestMapping("/persons")
class PersonController {

    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
        // ...
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}
URI模式

可以使用以下全域性模式和萬用字元對映請求:

  • ? 匹配一個字元
  • *匹配路徑段中的零個或多個字元
  • **匹配零個或多個路徑段

還可以宣告URI變數並使用@PathVariable訪問它們的值,如下例所示:

@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    // ...
}

可以在類和方法級別宣告URI變數,如下例所示:

@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {

    @GetMapping("/pets/{petId}")
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // ...
    }
}

URI變數將自動轉換為適當的型別,或者引發TypeMismatchException。預設情況下支援簡單型別(int、long、Date等),您可以註冊對任何其他資料型別的支援。

語法 {varName:正則表示式}宣告一個符合正則表示式的URI變數。 例如,給定URL“/spring-web-3.0.5.jar”,以下方法提取名稱、版本和副檔名:

@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
    // ...
}

URI路徑模式還可以具有嵌入的${…}佔位符,這些佔位符在啟動時通過對本地、系統、環境和其他屬性源使用PropertyPlaceHolderConfigurer進行解析。例如,你可以使用它根據一些外部配置引數化基URL。

Spring MVC 使用 PathMatcher和 AntPathMatcher解析URI路徑。

模式比較

當多個模式進行比較時,必須找到一個最佳匹配的URL。是通過使用AntPathMatcher.getPatternComparator(字串路徑),它查詢更具體的模式。

如果一個模式的URI變數(計數為1)、單萬用字元(計數為1)和雙萬用字元(計數為2)的數量較少,則該模式就不那麼具體。如果分數相等,則選擇較長的模式。在相同的分數和長度下,選擇URI變數多於萬用字元的模式。

預設的對映模式(/**)從評分中排除,並且總是最後排序。另外,字首模式(如/public/**)比其他沒有雙萬用字元的模式更不具體。

字尾匹配

預設情況下,springmvc執行.*字尾模式匹配,這樣對映到/person的控制器也隱式地對映到/person.*。然後使用副檔名解釋要用於響應的請求內容型別(即,不是Accept頭) - 例如/person.pdf, /person.xml,以及其他。

當瀏覽器用來發送難以一致解釋的Accept頭時,以這種方式使用副檔名是必要的。目前,這不再是必要的,使用Accept頭應該是首選。

隨著時間的推移,副檔名的使用已經被證明存在各種各樣的問題。當與使用URI變數、路徑引數和URI編碼重疊時,它可能導致歧義。

要完全禁用副檔名的使用,必須同時設定以下兩項:

  • 重寫WebMvcConfigurer下的configurePathMatch方法時,設定configurer.setUseSuffixPatternMatch(false)
  • 重寫WebMvcConfigurer下的configureContentNegotiation方法時,configurer.favorPathExtension(false)

基於URL的內容協商仍然有用(例如,在瀏覽器中鍵入URL時)。為此,我們建議使用基於查詢引數的策略,以避免副檔名帶來的大多數問題。或者,如果必須使用副檔名,請考慮通過ContentNegotiationConfigurer的mediaTypes屬性將其限制為顯式註冊的副檔名列表。

字尾匹配和RFD

反射檔案下載(RFD)攻擊與XSS類似,它依賴於在響應中反映的請求輸入(例如,查詢引數和URI變數)。然而,RFD攻擊不是在HTML中插入JavaScript,而是依賴於瀏覽器切換來執行下載,並在以後雙擊時將響應視為可執行指令碼。

在springmvc中,@ResponseBody和ResponseEntity方法存在風險,因為它們可以呈現不同的內容型別,客戶端可以通過URL路徑擴充套件請求這些內容型別。禁用字尾模式匹配和使用路徑擴充套件進行內容協商可以降低風險,但不足以防止RFD攻擊。

為了防止RFD攻擊,在呈現響應體之前,springmvc添加了一個Content-Disposition:inline;filename=f.txt頭,來建議一個固定和安全的下載檔案。只有當URL路徑包含既沒有白名單也沒有為內容協商明確註冊的副檔名時,才會執行此操作。然而,當直接在瀏覽器中輸入url時,它可能會產生副作用。

預設情況下,許多公共路徑擴充套件都是白名單。具有自定義HttpMessageConverter實現的應用程式可以顯式註冊用於內容協商的副檔名,以避免為這些副檔名新增內容處置頭。

Consumable Media Types

可以根據請求的內容型別縮小請求對映範圍,如下例所示:

@PostMapping(path = "/pets", consumes = "application/json") 
public void addPet(@RequestBody Pet pet) {
    // ...
}

consumers屬性還支援否定表示式 - ,例如!text/plain指除text/plain之外的任何內容型別。

可以在類級別宣告共享的consumes屬性。但是,與大多數其他請求對映屬性不同,當在類級別使用時,方法級別使用屬性重寫而不是擴充套件類級別宣告。

MediaType為常用的媒體型別提供常量,如APPLICATION_JSON_VALUE和APPLICATION_XML_VALUE。

Producible Media Types

你可以根據Accept request標頭和控制器方法生成的內容型別列表縮小請求對映範圍,如下例所示:

@GetMapping(path = "/pets/{petId}", produces = "application/json;charset=UTF-8") 
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}

媒體型別可以指定字符集。反表示式支援 - 例如!text/plain指除“text/plain”之外的任何內容型別。

可以在類級別宣告共享的products屬性。但是,與大多數其他請求對映屬性不同的是,當在類級別使用時,方法級別生成屬性重寫,而不是擴充套件類級別宣告。

MediaType為常用的媒體型別提供常量,例如APPLICATION_JSON_UTF8峎u VALUE和APPLICATION_XML_VALUE。

對於JSON內容型別,即使RFC7159明確宣告“沒有為此註冊定義字符集引數”,也應該指定UTF-8字符集,因為有些瀏覽器要求它正確解釋UTF-8特殊字元。

Parameters, headers

你可以根據請求引數條件縮小請求對映的範圍。你可以測試是否存在請求引數(myParam),是否存在請求引數(!或特定值(myParam=myValue)。以下示例顯示如何測試特定值:

@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") 
public void findPet(@PathVariable String petId) {
    // ...
}

這個例子測試myParam是否等於myValue。

你也可以在請求頭條件中使用相同的方法,如下例所示:

@GetMapping(path = "/pets", headers = "myHeader=myValue") 
public void findPet(@PathVariable String petId) {
    // ...
}

這個例子測試myHeader是否等於myValue

你可以將內容型別和Accept與headers條件匹配,但最好使用consumes和products。

HTTP頭,選項

@GetMapping(和@RequestMapping(方法=HttpMethod.GET))透明地支援HTTP頭進行請求對映。控制器方法不需要更改。響應包裝器,應用於javax.servlet.http.HttpServlet,確保將內容長度頭設定為寫入的位元組數(而不是實際寫入響應)。

@GetMapping(和@RequestMapping(方法=HttpMethod.GET))隱式對映到並支援HTTP HEAD。httphead請求被當作httpget來處理,除了不寫入主體,而是計算位元組數並設定Content-Length頭。

預設情況下,通過將Allow response頭設定為在具有匹配URL模式的所有@RequestMapping方法中列出的HTTP方法列表來處理HTTP選項。

對於沒有HTTP方法宣告的@RequestMapping,Allow頭設定為GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS。控制器方法應該始終宣告支援的HTTP方法(例如,通過使用HTTP方法特定的變體:@GetMapping、@PostMapping等)。

你可以顯式地將@RequestMapping方法對映到HTTP頭和HTTP選項,但在普通情況下這不是必需的。

自定義註解

spring mvc支援對請求對映使用組合註解。這些註解本身是用@RequestMapping進行元註解的,並且組合起來是為了重新宣告@RequestMapping屬性的一個子集(或全部),目的更窄、更具體。

@GetMapping、@PostMapping、@PutMapping、@DeleteMapping和@PatchMapping是組合註解的示例。之所以提供它們是因為,大多數控制器方法應該對映到特定的HTTP方法,而不是使用@RequestMapping,後者在預設情況下匹配所有HTTP方法。

spring mvc還支援具有定製請求匹配邏輯的定製請求對映屬性。這是一個更高階的選項,需要子類化RequestMappingHandlerMapping並重寫getCustomMethodCondition方法,在該方法中,你可以檢查自定義屬性並返回自己的RequestCondition。

顯式註冊

你可以以程式設計方式註冊處理程式方法,這些方法可用於動態註冊或高階情況,例如同一處理程式在不同URL下的不同例項。以下示例註冊了處理程式方法:

@Configuration
public class MyConfig {

    @Autowired
    public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) 
            throws NoSuchMethodException {

        RequestMappingInfo info = RequestMappingInfo
                .paths("/user/{id}").methods(RequestMethod.GET).build(); 

        Method method = UserHandler.class.getMethod("getUser", Long.class); 

        mapping.registerMapping(info, handler, method); 
    }

}
處理方法(Handler Methods)

@RequestMapping處理程式方法具有靈活的簽名,可以從一系列受支援的控制器方法引數和返回值中進行選擇。

方法引數

JDK 8的java.util.Optional與具有必需屬性(例如,@RequestParam、@RequestHeader等)且等效於required=false的註解一起支援作為方法引數。

下列描述了支援的控制器方法引數。

WebRequest, NativeWebRequest

對請求引數、請求和會話屬性的通用訪問,無需直接使用Servlet API。

javax.servlet.ServletRequest, javax.servlet.ServletResponse

選擇任何特定的請求或響應型別 - 例如,ServletRequest、HttpServletRequest或Spring的MultipartRequest、MultipartHttpServletRequest。

javax.servlet.http.HttpSession

強制會話的存在。請注意,會話訪問不是執行緒安全的。如果允許多個請求同時訪問一個會話,請考慮將RequestMappingHandlerAdapter例項的synchronizeOnSession標誌設定為true。

javax.servlet.http.PushBuilder

用於程式設計HTTP/2資源推送的Servlet 4.0推送構建器API。注意,根據Servlet規範,如果客戶機不支援HTTP/2功能,那麼注入的PushBuilder例項可以為null。

java.security.Principal

當前經過身份驗證的使用者 - 可能是特定的主體實現類。

HttpMethod

請求的HTTP方法。

java.util.Locale

當前請求的區域,由可用的最具體的LocaleResolver確定(實際上,配置的LocaleResolver或LocaleContextResolver)。

java.io.InputStream, java.io.Reader

訪問由Servlet API公開的原始請求體。

java.io.OutputStream, java.io.Writer

訪問由Servlet API公開的原始響應體。

@PathVariable

用於訪問URI模板變數。

@MatrixVariable

用於訪問URI路徑段中的名稱-值對

@RequestParam

用於訪問Servlet請求引數,包括multipart files。引數值轉換為宣告的方法引數型別。\

注意,對於簡單的引數值,使用@RequestParam是可選的。

@RequestHeader

用於訪問請求頭。值轉換為宣告的方法引數型別

@CookieValue

獲取cookies。Cookie值將轉換為宣告的方法引數型別。

@RequestBody

用於訪問HTTP請求主體。使用HttpMessageConverter實現將正文內容轉換為宣告的方法引數型別

HttpEntity<B>

用於訪問請求頭和正文。主體使用HttpMessageConverter進行轉換

@RequestPart

用於訪問multipart/form-data的請求中的部件,請使用HttpMessageConverter轉換部件的主體。

java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap

用於訪問HTML控制器中使用的模型,並作為檢視呈現的一部分公開給模板。

RedirectAttributes

指定在重定向時使用的屬性(即附加到查詢字串中)和在重定向後的請求之前臨時儲存的flash屬性。

@ModelAttribute

用於訪問模型中的現有屬性(如果不存在,則例項化),並應用資料繫結和驗證。

請注意,@ModelAttribute的使用是可選的(例如,設定其屬性)。

Errors, BindingResult

用於訪問命令物件(即@ModelAttribute引數)的驗證和資料繫結中的錯誤,或@RequestBody或@RequestPart引數驗證中的錯誤。必須在驗證的方法引數後立即宣告錯誤或BindingResult引數。

UriComponentsBuilder

用於準備與當前請求的主機、埠、方案、上下文路徑和servlet對映的文字部分相關的URL。

@SessionAttribute

訪問任何會話屬性,這與類級別@SessionAttributes宣告儲存在會話中的模型屬性不同

@RequestAttribute

用於訪問請求屬性。

其他引數

如果某個方法引數與這裡說的這些引數中的任何值都不匹配,並且它是一個簡單型別(由BeanUtils#isSimpleProperty確定),則它被解析為@RequestParam。否則,它將被解析為@ModelAttribute。

返回值

下列描述了控制器支援的返回值。

@ResponseBody

返回值通過HttpMessageConverter實現進行轉換並寫入響應體內。

HttpEntity<B>, ResponseEntity<B>

指定完整響應(包括HTTP頭和正文)的返回值將通過HttpMessageConverter實現進行轉換並寫入響應。

HttpHeaders

返回一個沒有正文的頭的響應。

String

要與ViewResolver實現一起解析並與通過命令物件和@ModelAttribute方法確定的隱式模型 - 一起使用的檢視名稱。

View

要與通過命令物件和@ModelAttribute方法確定的隱式模型 - 一起渲染的檢視例項。

java.util.Map, org.springframework.ui.Model

要新增到隱式模型的屬性,檢視名稱通過RequestToViewNameTranslator隱式確定。

@ModelAttribute

要新增到模型中的屬性,檢視名稱通過RequestToViewNameTranslator隱式確定。

請注意,@ModelAttribute是可選的。

ModelAndView

要使用的檢視和模型屬性以及(可選)響應狀態。

void

如果具有void返回型別(或null返回值)的方法還具有ServletResponse、OutputStream引數或@ResponseStatus註釋,則認為該方法已完全處理了響應。

DeferredResult<V>

Callable<V>

在spring mvc託管執行緒中非同步生成上述任何返回值

ListenableFuture<V>, java.util.concurrent.CompletionStage<V>, java.util.concurrent.CompletableFuture<V>

為了方便起見(例如,當底層服務返回其中一個時),可以選擇DeferredResult。

ResponseBodyEmitter, SseEmitter

使用HttpMessageConverter實現非同步發出要寫入響應的物件流。也支援作為迴應的主體。

StreamingResponseBody

非同步寫入響應OutputStream。也支援作為迴應的主體。

型別轉換

某些表示基於字串的請求輸入的帶註解的控制器方法引數(例如@RequestParam、@RequestHeader、@PathVariable、@MatrixVariable和@CookieValue)可能需要型別轉換,前提是該引數宣告為字串以外的內容。

對於這種情況,型別轉換將根據配置的轉換器自動應用。預設情況下,支援簡單型別(int、long、Date和其他型別)。你可以通過WebDataBinder或通過向FormattingConversionService註冊格式化程式來自定義型別轉換。

矩陣變數(Matrix Variables)

矩陣變數可以出現在任何路徑段中,每個變數用分號分隔,多個值用逗號分隔(例如,/cars;color=red,green;year=2012)。也可以通過重複的變數名指定多個值(例如,color=red;color=green;color=blue)。

如果URL包含矩陣變數,則控制器方法的請求對映必須使用URI變數來遮蔽該變數內容,並確保可以成功匹配請求,而不依賴於矩陣變數的順序和存在。以下示例使用矩陣變數:

// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11
}

考慮到所有路徑段都可能包含矩陣變數,你有時可能需要排除矩陣變數應該在哪個路徑變數中的歧義。下面的示例演示如何執行此操作:

// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 

矩陣變數可以定義為可選,並指定預設值,如下例所示:

// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1
}

要獲取所有矩陣變數,可以使用多值對映,如下例所示:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap<String, String> matrixVars,
        @MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 22, "s" : 23]
}

請注意,你需要啟用矩陣變數的使用。在MVC-Java配置中,需要通過路徑匹配設定一個UrlPathHelper,removeSemicolonContent=false。在mvc xml名稱空間中,可以設定<mvc:annotation-driven enable-matrix-variables="true"/>.

@RequestParam

可以使用@RequestParam註解將Servlet請求引數(即查詢引數或表單資料)繫結到控制器中的方法引數。

@Controller
@RequestMapping("/pets")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, Model model) { 
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}

預設情況下,使用此註解的方法引數是必需的,但是可以通過將@RequestParam註釋的required標誌設定為false或通過使用java.util.Optional

如果目標方法引數型別不是字串,則自動應用型別轉換。

將引數型別宣告為陣列或列表允許解析同一引數名的多個引數值。

如果@RequestParam註解宣告為Map<String,String>或MultiValueMap<String,String>,但沒有在註解中指定引數名,則會使用每個給定引數名的請求引數值填充對映。

請注意,使用@RequestParam是可選的(例如,設定其屬性)。預設情況下,任何簡單值型別(由BeanUtils#isSimpleProperty確定)且未由任何其他引數解析程式解析的引數都將被視為使用@RequestParam註釋。

@RequestHeader

可以使用@RequestHeader註解將請求頭繫結到控制器中的方法引數。

看下以下這個有請求頭的請求:

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
@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding, 
        @RequestHeader("Keep-Alive") long keepAlive) { 
    //...
}

如果目標方法引數型別不是字串,則自動應用型別轉換。

如果@RequestParam註解宣告為Map<String,String>或MultiValueMap<String,String>,但沒有在註解中指定引數名,則會使用每個給定引數名的請求引數值填充對映。

內建支援可用於將逗號分隔的字串轉換為陣列或字串集合或型別轉換系統轉換為已知的其他型別。例如,用@RequestHeader(“Accept”)註解的方法引數可以是String型別,但也可以是String[]或List<String>。

@CookieValue

可以使用@CookieValue註解將http cookie的值繫結到控制器中的方法引數。

考慮具有以下cookie的請求:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

以下示例展示如何獲取cookie值:

@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) { 
    //...
}

如果目標方法引數型別不是字串,則自動應用型別轉換。

@ModelAttribute

你可以在方法引數上使用@ModelAttribute註解來訪問模型中的屬性,或者在不存在的情況下將其例項化。model屬性還覆蓋了來自httpservlet請求引數的值,這些引數的名稱與欄位名匹配。這稱為資料繫結,它使你不必處理單個查詢引數和表單欄位的解析和轉換。下面的示例演示如何執行此操作:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { } 
@SessionAttributes

@SessionAttributes用於在請求之間的httpservlet會話中儲存模型屬性。它是一個型別級別的註解,宣告特定控制器使用的會話屬性。這通常會列出模型屬性的名稱或模型屬性的型別,這些屬性應該透明地儲存在會話中,以供後續請求訪問。

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

在第一個請求中,當一個名為pet的模型屬性新增到模型中時,它會自動升級到httpservlet會話並儲存在其中。在另一個控制器方法使用SessionStatus方法引數清除儲存之前,它一直保持在那裡,如下例所示:

@Controller
@SessionAttributes("pet") 
public class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    public String handle(Pet pet, BindingResult errors, SessionStatus status) {
        if (errors.hasErrors) {
            // ...
        }
            status.setComplete(); 
            // ...
        }
    }
}
@SessionAttribute

如果你需要訪問全域性管理的預先存在的會話屬性(例如,在控制器 - - 之外),並且可能存在也可能不存在,則可以對方法引數使用@SessionAttribute註解,如下例所示:

@RequestMapping("/")
public String handle(@SessionAttribute User user) { 
    // ...
}

對於需要新增或刪除會話屬性的用例,請考慮注入org.springframework.web.context.request.WebRequest或者javax.servlet.http.HttpSession匯入控制器方法。

對於作為控制器工作流的一部分在會話中臨時儲存模型屬性,請考慮使用@SessionAttributes。

@RequestAttribute

與@SessionAttribute類似,你可以使用@RequestAttribute註解來訪問先前建立的請求屬性(例如,通過Servlet過濾器或HandlerInterceptor):

@GetMapping("/")
public String handle(@RequestAttribute Client client) { 
    // ...
}
重定向屬性(Redirect Attributes)
Flash Attributes
Multipart

啟用MultipartResolver後,帶有multipart/form-data的POST請求的內容將被解析並作為常規請求引數訪問。以下示例訪問一個常規表單欄位和一個上傳的檔案:

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }
        return "redirect:uploadFailure";
    }
}

將引數型別宣告為List<MultipartFile>允許解析同一引數名的多個檔案。

如果@RequestParam註解宣告為Map<String,MultipartFile>或MultiValueMap<String,MultipartFile>,但沒有在註解中指定引數名,則對映將填充每個給定引數名的multipart files。

對於Servlet3.0的multipart解析,你還可以宣告javax.servlet.http.Part而不是Spring的MultipartFile,作為方法引數或集合值型別。

@RequestBody

你可以使用@RequestBody註解通過HttpMessageConverter將請求體讀取並反序列化為物件。以下示例使用@RequestBody引數:

@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
    // ...
}

可以將@RequestBody與javax.validation.Valid或者Spring的@Validated註釋,這兩種方法都會導致應用標準Bean驗證。預設情況下,驗證錯誤會導致MethodArgumentNotValidException,該異常會變成400(BAD_REQUEST)響應。或者,可以通過errors或BindingResult引數在控制器內部本地處理驗證錯誤,如下例所示:

@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
    // ...
}
HttpEntity

HttpEntity與使用@RequestBody類似,它基於一個公開請求頭和主體的容器物件。

@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
    // ...
}
@ResponseBody

可以在方法上使用@ResponseBody註釋,通過HttpMessageConverter將返回序列化到響應主體。下面的列表顯示了一個示例:

@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
    // ...
}

@ResponseBody在類級別也受支援,在這種情況下,它由所有控制器方法繼承。這是@RestController的效果,它只不過是一個用@Controller和@ResponseBody標記的元註解。

ResponseEntity

ResponseEntity類似於@ResponseBody,但有狀態和響應頭。例如:

@GetMapping("/something")
public ResponseEntity<String> handle() {
    String body = ... ;
    String etag = ... ;
    return ResponseEntity.ok().eTag(etag).build(body);
}
Jackson JSON

Spring提供了對jacksonjson庫的支援。

springmvc為Jackson的序列化檢視提供了內建支援,後者只允許呈現物件中所有欄位的子集。要將其與@ResponseBody或ResponseEntity控制器方法一起使用,可以使用Jackson的@JsonView註解啟用序列化檢視類,如下例所示:

@RestController
public class UserController {

    @GetMapping("/user")
    @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;
    }
}

@JsonView允許一個檢視類陣列,但是每個控制器方法只能指定一個。如果需要啟用多個檢視,可以使用複合介面。

對於依賴檢視解析度的控制器,可以將序列化檢視類新增到模型中,如下例所示:

@Controller
public class UserController extends AbstractController {

    @GetMapping("/user")
    public String getUser(Model model) {
        model.addAttribute("user", new User("eric", "7!jd#h23"));
        model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
        return "userView";
    }
}
模型(Model)

你可以使用@ModelAttribute註釋:

  • 在@RequestMapping方法中的方法引數上建立或訪問模型中的物件,並通過WebDataBinder將其繫結到請求。
  • 作為@Controller或@ControllerAdvice類中的方法級註釋,有助於在呼叫任何@RequestMapping方法之前初始化模型。
  • 在@RequestMapping方法上標記其返回值的是一個model屬性。

@ModelAttribute方法具有靈活的方法簽名。它們支援許多與@RequestMapping方法相同的引數,除了@ModelAttribute本身或任何與請求正文相關的引數。

以下示例顯示了@ModelAttribute方法:

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountRepository.findAccount(number));
    // a
}

以下示例僅新增一個屬性:

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountRepository.findAccount(number);
}
資料繫結(DataBinder)

@Controller或@ControllerAdvice類可以具有初始化WebDataBinder例項的@InitBinder方法,而這些方法又可以:

  • 將請求引數(即表單或查詢資料)繫結到模型物件。
  • 將基於字串的請求值(如請求引數、路徑變數、頭、cookie等)轉換為控制器方法引數的目標型別。
  • 呈現HTML表單時,將模型物件值格式化為字串值。

@InitBinder方法可以註冊特定於控制器的java.bean.PropertyEditor或Spring Converter和 Formatter元件。此外,你可以使用MVC配置在全域性共享的FormattingConversionService服務中Converter和 Formatter。

@InitBinder方法支援許多與@RequestMapping方法相同的引數,但@ModelAttribute引數除外。通常,它們用WebDataBinder引數(用於註冊)和void返回值宣告。下面的列表顯示了一個示例:

@Controller
public class FormController {

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

    // ...
}

或者,通過共享FormattingConversionService使用基於Formatter的設定時,可以重用相同的方法並註冊特定於控制器的格式化程式實現,如下例所示:

@Controller
public class FormController {

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

    // ...
}
異常(Exceptions)

@Controller和@ControllerAdvice類可以使用@ExceptionHandler方法來處理來自控制器方法的異常,如下例所示:

@Controller
public class SimpleController {

    // ...

    @ExceptionHandler
    public ResponseEntity<String> handle(IOException ex) {
        // ...
    }
}

異常可能與正在傳播的頂級異常(即引發的直接IOException)匹配,也可能與頂級包裝異常中的直接原因匹配(例如,在IllegalStateException中包裝的IOException)。

對於匹配異常型別,最好將目標異常宣告為方法引數,如前面的示例所示。當多個異常方法匹配時,根異常匹配通常優於原因異常匹配。更具體地說,ExceptionDepthComparator用於根據異常型別的深度對異常進行排序。

或者,註解宣告可以縮小異常型別以匹配,如下例所示:

@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
    // ...
}

你甚至可以使用帶有非常通用引數簽名的特定異常型別列表,如下例所示:

@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
    // ...
}

我們通常建議你在引數簽名中儘可能具體,以減少根異常型別和原因異常型別之間不匹配的可能性。考慮將一個多匹配方法分解為單獨的@ExceptionHandler方法,每個方法通過其簽名匹配一個特定的異常型別。

spring mvc中對@ExceptionHandler方法的支援是建立在DispatcherServlet級別的HandlerExceptionResolver機制上的。

方法引數

@ExceptionHandler方法支援以下引數:

增強Controller(Controller Advice)

三、URI 連結

本節介紹Spring框架中可用於處理URI的各種選項。

UriComponents

UriComponentsBuilder幫助從帶有變數的URI模板構建URI,如下例所示:

四、非同步請求

Spring MVC與Servlet 3.0非同步請求處理進行了廣泛的整合:

  • 控制器方法中的DeferredResult和Callable返回值,並提供對單個非同步返回值的基本支援。
  • 控制器可以流式傳輸多個值,包括SSE和原始資料。
  • 控制器可以使用反應式客戶端並返回反應式型別進行響應處理。
DeferredResult

一旦在Servlet容器中啟用了非同步請求處理功能,控制器方法就可以用DeferredResult包裝任何受支援的控制器方法返回值,如下例所示:

@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// From some other thread...
deferredResult.setResult(data);

控制器可以從不同的執行緒非同步生成返回值,例如,響應外部事件(JMS訊息)、計劃任務或其他事件。

Callable

控制器可以用java.util.concurrent.Callable,如下例所示:

@PostMapping
public Callable<String> processUpload(final MultipartFile file) {

    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };

}

可以通過配置的TaskExecutor執行給定的任務來獲得返回值。

處理過程

下面是Servlet非同步請求處理的一個非常簡潔的概述:

  • ServletRequest可以通過呼叫request.startAsync(). 這樣做的主要效果是Servlet(以及任何過濾器)可以退出,但是響應保持開啟狀態,以便稍後完成處理。
  • 呼叫request.startAsync()返回AsyncContext,您可以使用它進一步控制非同步處理。例如,它提供了dispatch方法,類似於Servlet API的轉發,只是它允許應用程式在Servlet容器執行緒上恢復請求處理。
  • ServletRequest提供對當前DispatcherType的訪問,你可以使用它來區分處理初始請求、非同步排程、轉發和其他排程程式型別。

DeferredResult處理如下:

  • 控制器返回一個DeferredResult,並將其儲存在記憶體佇列或列表中,以便訪問它。
  • Spring MVC呼叫request.startAsync()
  • DispatcherServlet和所有配置的過濾器退出請求處理執行緒,但響應保持開啟狀態。
  • 應用程式從某個執行緒設定DeferredResult,spring mvc將請求發回Servlet容器。
  • 再次呼叫DispatcherServlet,並使用非同步生成的返回值繼續處理。

Callable處理的工作方式如下:

  • Controller返回一個Callable
  • Spring MVC呼叫request.startAsync()並將可呼叫項提交給TaskExecutor以在單獨的執行緒中進行處理。
  • DispatcherServlet和所有配置的過濾器退出請求處理執行緒,但響應保持開啟狀態。
  • 最終,Callable生成一個結果,spring mvc將請求傳送回Servlet容器以完成處理。
  • 再次呼叫DispatcherServlet,並使用可呼叫的非同步生成的返回值繼續處理。
異常處理

使用DeferredResult時,可以選擇呼叫setResult還是帶有異常的setErrorResult。在這兩種情況下,springmvc將請求發回Servlet容器以完成處理。然後,它要麼被視為控制器方法返回了給定的值,要麼就好像它產生了給定的異常。然後,異常通過常規異常處理機制(例如,呼叫@ExceptionHandler方法)。

使用Callable時,會出現類似的處理邏輯,主要區別在於結果是從Callable返回的,或者由Callable引發異常。

攔截

HandlerInterceptor例項可以是AsyncHandlerInterceptor型別,用於在啟動非同步處理的初始請求(而不是postHandle和afterCompletion)上接收afterConcurrentHandlingStarted回撥。

HandlerInterceptor實現還可以註冊CallableProcessingInterceptor或DeferredResultProcessingInterceptor,以便與非同步請求的生命週期更深入地整合(例如,處理超時事件)。

DeferredResult提供onTimeout(可執行)和onCompletion(可執行)回撥。

HTTP流

你可以對單個非同步返回值使用DeferredResult和Callable。如果要生成多個非同步值並將這些值寫入響應,該怎麼辦?本節介紹如何執行此操作。

Objects

可以使用ResponseBodyEmitter返回值生成物件流,其中每個物件都使用HttpMessageConverter序列化並寫入響應,如下例所示:

@GetMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();

你還可以將ResponseBodyEmitter用作ResponseEntity中的主體,允許你自定義響應的狀態和請求頭。

當emitter丟擲IOException時(例如,如果遠端客戶機退出),應用程式不負責清理連線,也不應呼叫emitter.complete 或者 emitter.completeWithError。servlet容器會自動啟動AsyncListener錯誤通知,spring mvc在該通知中發出completeWitherError呼叫。這個呼叫反過來對應用程式執行最後一次非同步分派,在此期間spring mvc呼叫配置的異常解析器並完成請求。

SSE

SseEmitter(ResponseBodyEmitter的一個子類)提供對伺服器傳送事件的支援,其中從伺服器傳送的事件是根據W3C SSE規範格式化的。要從控制器生成SSE流,請返回SSEEnter,如下例所示

@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
    SseEmitter emitter = new SseEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();

雖然SSE是流式傳輸到瀏覽器的主要選項,但請注意,Internet Explorer不支援伺服器傳送的事件。考慮將Spring的WebSocket訊息傳遞與針對多種瀏覽器的SockJS回退傳輸(包括SSE)一起使用。

原始資料(Raw Data)

有時,繞過訊息轉換並直接流到響應輸出流(例如,用於檔案下載)是很有用的。可以使用StreamingResponseBody返回值型別來執行此操作,如下例所示:

@GetMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // write...
        }
    };
}

你可以使用StreamingResponseBody作為ResponseEntity中的主體,以自定義響應的狀態和標頭。

斷開連線

當遠端客戶機離開時,Servlet API不提供任何通知。因此,當流式傳輸到響應時,無論是通過SseEmitter還是reactive型別,都必須定期傳送資料,因為如果客戶端斷開連線,寫入將失敗。傳送的形式可以是空的SSE事件或任何其他資料,而另一方必須將其解釋為心跳訊號並忽略這些資料。

或者,考慮使用具有內建心跳機制的web訊息傳遞解決方案(例如用SockJS對WebSocket或WebSocket使用SockJS)

配置

非同步請求處理功能必須在Servlet容器級別啟用。MVC配置還為非同步請求公開了幾個選項。

Servlet容器

過濾器和Servlet宣告有一個asyncSupported標誌,需要將其設定為true才能啟用非同步請求處理。另外,應該宣告過濾器對映來處理非同步javax.servlet.DispatchType.

在Java配置中,當你使用AbstractAnnotationConfigDispatcherServletInitializer初始化Servlet容器時,這是自動完成的。

在web.xml檔案配置時,可以將<async supported>true</async supported>新增到DispatcherServlet和過濾器宣告,並將<dispatcher>ASYNC</dispatcher>新增到過濾器的mapping。

Spring MVC

MVC配置公開了以下與非同步請求處理相關的選項:

  • Java配置:在WebMVCConfiguer上使用configureAsyncSupport回撥。
  • XML名稱空間:在<mvc:annotation-driven>.下使用<async-support>

你可以配置以下內容:

  • 非同步請求的預設超時值,如果未設定,則取決於底層Servlet容器(例如,在Tomcat上為10秒)。
  • AsyncTaskExecutor,用於在使用反應型別進行流式處理時阻止寫入,並用於執行從控制器方法返回的Callable例項。如果你使用反應式型別進行流式處理或具有返回Callable的控制器方法,我們強烈建議您配置此屬性,因為預設情況下,它是一個SimpleAsynctAsExecutor。
  • DeferredResultProcessingInterceptor實現和CallableProcessingInterceptor實現。

請注意,您還可以設定DeferredResult、ResponseBodyEmitter和SseEmitter的預設超時值。對於可呼叫的,可以使用WebAsyncTask提供超時值。