原文鏈接:https://www.codemore.top/cates/Backend/post/2018-04-21/spring-mvc-handler-methods 由註解@RequestMapping註解修飾的處理請求的函數的簽名非常的靈活,可以使用controller函數支持的一系列參數 ...
原文鏈接:https://www.codemore.top/cates/Backend/post/2018-04-21/spring-mvc-handler-methods
由註解@RequestMapping
註解修飾的處理請求的函數的簽名非常的靈活,可以使用controller函數支持的一系列參數和返回值。
函數參數
下列表格列出了controller方法可以接受的參數,稍後會對其進行詳細的解釋。 對於 JDK 8的java.util.Optional
可以在包含required
屬性的註解中使用,例如:@RequestParam
,@RequestHeader
等,相當於required=false
函數參數 | 解釋 |
---|---|
WebRequest ,NativeWebRequest |
無需直接使用Servlet API來訪問請求參數,請求屬性和session的屬性。 |
javax.servlet.ServletRequest ,javax.servlet.ServletResponse |
可以是指定的請求和響應類型例如ServletRequest ,HttpServletRequest ,也可以是Spring的MultipartRequest ,MultipartHttpServletRequest |
HttpSession |
參數永不為null,訪問session非線程安全,如果多個請求訪問一個session,需要設置RequestMappingHandlerAdapter 的synchronizeOnSession 為true。 |
PushBuilder |
Servlet 4.0 支持HTTP/2 push的API,如果客戶端不支持則為null |
Principal |
當前授權用戶。 |
HttpMethod |
請求的HTTP方法 |
Locale |
當前請求的區域,由LocaleResolver 解析 |
TimeZone,ZoneId |
由LocaleContextResolver 解析的當前請求的時區 |
InputStream ,Reader |
訪問由Servlet API暴露的請求體 |
OutputStream ,Writer |
訪問由Servlet API 暴露的響應體 |
@PathVairable |
訪問URI變數 |
@MatrixVariable |
訪問URI中的name-value值。例如 pets/42;q=11 @MatrixVariable int q |
@Requestparam |
訪問請求中參數 |
@RequestHeader |
訪問請求頭 |
@CookieValue |
訪問cookie值 |
@RequestBody |
訪問請求體,將請求體轉換為相應類型 |
HttpEntity<B> |
訪問請求頭和請求體 |
Map ,Model ,ModelMap |
訪問會在渲染模板時使用的變數。 |
RedirectAttributes |
重定向時使用的屬性 |
@ModelAttribute |
訪問model中的屬性,同時進行數據綁定和校驗 |
Errors, BindingResult | 訪問在數據綁定和校驗是出現的錯誤。 |
類級別的 @SessionAttributes
,SessionStatus | 在不同的請求中存儲session UriComponentsBuilder
| 相對於當前請求的host,port,scheme等 @SessionAttribute
| 訪問session中的屬性 @RequestAttribute
| 訪問請求的屬性。 其他類型
| 如果參數非上述類型,那麼將當成@RequestParam
來處理
返回值
下列表格列出了支持的返回類型
返回值類型 | 解釋 |
---|---|
@ResponseBody |
返回值由HttpMessageConverters 轉換,直接寫到響應體 |
HttpEntity<B> , ResponseEntity<B> |
返回值包括,http header和body |
HttpHeaders |
只返回HTTP header |
String |
由ViewResolver 解析出具體的模板渲染。 |
View |
返回具體的視圖 |
Map ,Model |
model包含的屬性,視圖由RequestToViewNameTranslator 解析 |
@ModelAttribute |
返回添加到Model的屬性,視圖由RequestToViewNameTranslator 解析. |
ModelAndView |
返回具體視圖和添加的model |
void |
返回void,則Spring MVC會認為Controller內部已經處理好響應內容了。 |
DeferredResult<V> |
非同步返回結果,可以由任意線程處理 |
Callback<V> |
非同步返回,現成由Spring MVC管理 |
ListenableFuture<V> ,CompletionStage<V> ,CompletableFuture<V> |
同DefferedResult |
ResponseBodyEmitter ,SseEmitter |
使用HttpMessageConverter 非同步將對象以流的方式發到響應。 |
StreamingResponseBody |
非同步將響應發送的輸出流 |
Reactor, RxJava, 等Reactive類型 | 同DeferredResult |
其他類型 | 如果不返回以上類型,預設當作視圖名稱處理。 |
類型轉換
一些需要參數的註解,例如@RequestParam
,@RequestHeader
,@PathVariabl
,@MatrixVariable
和@CookieValue
,如果他麽的參數並非String,那麼久需要進行類型轉換。 類型轉換自動由Spring MVC中註冊的轉換器來進行轉換,預設情況下支持,int,long,Date等簡單類型。對於不支持的類型可以通過WebDataBinder
或者由FormattingConversionService
註冊的Formatter
來進行轉換。
Matrix 變數
RFC 3986規定了在路徑中添加name-value對。在Spring MVC中,將其定義為matrix變數。 Matrix變數可以出現在任意的路徑中,每個變數由分號隔開,多個值由逗號隔開,例如:/cars;color=red,green;year=2012
。多個值同樣可也可是通過分離名字來指定,例如:color=red;color=green
。 如果想要在路徑中添加Matrix變數,那麼就必須保證相應的controller方法包含接收matrix變數,並且請求映射不收到Matrix變數的影響。例如:
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
// q == 11
}
因為所有的路徑都有可能包辦Matrix變數,可以通過指定路徑的形式分辨某個Matrix變數屬於哪個路徑例如:
@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 == 22
}
Matrix變數可以是可選的,指定預設值.例如:
// GET /pets/42
@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
// q == 1
}
可以使用MultiValueMap
獲取所有的Matrix變數,例如:
// 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]
}
預設情況下Spring MVC是不啟用Matrix變數的,如果是用Java配置,可以通過配置UrlPathHelper
的removeSemicolonContent=false
啟用,如果是使用XML配置,可以使用<mvc:annotation-driven enable-matrix=variable="true"/>
啟用。
@RequestParam
@RequestParam
可以將Servlet請求參數綁定到controller函數中的變數.例如:
@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
預設情況下是true
,如果不希望必須指定某個參數可以設置required=false
或者如果使用Java 8 可以使用java.util.Optional
。 如果函數的參數非String類型,那麼將會進行自動類型轉換。 如果@RequsetParam
修飾的是Map<String,String>
或者MultiValueMap<String,String>
那麼就會獲取所有的請求參數。
@RequestHeader
@RequestHeader
將header的值綁定到controller的方法參數中。 例如一下作為請求header:
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
header
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
同樣,如果參數非String類型,也會自動進行類型轉換,如果修飾的是Map<String,String>
,MultiValueMap<String,String>
或者HttpHeaders
,也是獲取所有的header值
@CookieValue
使用@CookieValue
將cookie值綁定到controller的方法參數中 例如以下cookie:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
下列代碼即可獲取:
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
//...
}
同樣的,如果參數類型非String,會自動進行類型轉換。
@ModelAttribute
使用@ModelAttribute
修飾的函數參數可以訪問Model中的屬性,或者其未初始化是初始化。方法參數名和請求參數名相同,model 屬性同樣也可以覆蓋其請求參數,這樣就不需要自己再從請求參數中解析了。例如:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }
Pet 示例的獲取:
- 如果Model中存在,則從Model中解析
- 通過
@SessionAttributes
獲取 - 從URI的路徑變數中獲取
- 通過預設的構造函數獲取
- 通過和Servlet請求參數相匹配的帶參數的構造函數獲取。參數名由
@ConstructorProperties
獲取或者位元組碼獲取。
當然一般都是使用Model來填充其值的,另一個選擇使用URI的路徑變數,其值通過註冊的Converter<String,T>
轉換。下麵這個例子就是@ModelAttribute
修飾的值和路徑匹配,通過Converter<String,Account>
進行類型轉換。
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
// ...
}
獲取了屬性值的實例後就可以開始進行數據綁定了。WebDataBinder
類通過匹配Servlet 的請求參數名(查詢參數和form欄位)來將欄位名對應到對象中。當類型轉換完之後填充匹配的欄位。DataBinder
和Validation
將在後面章節詳細描述。 數據綁定是會產生錯誤的,預設情況下會拋出BindException
異常,為了在controller的方法中捕獲這個異常,可以在方法參數中加入BindingResult
獲取異常。例如:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
某些情況下只想要訪問屬性之而不需要數據綁定。這種情況下可以將設置@ModelAttribute(binding=false)
。例如:
@ModelAttribute
public AccountForm setUpForm() {
return new AccountForm();
}
@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
return accountRepository.findOne(accountId);
}
@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
@ModelAttribute(binding=false) Account account) {
// ...
}
添加javax.util.validation.Valid
或者Spring的@Validated
註解,在數據綁定完成後會自動校驗。例如:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
@SessionAttributes
@SessionAttributes
用於在不同的請求間存儲Servlet session的屬性值。主要是列出需要在接下來的請求訪問的session的值自動的保存到session中。例如:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
第一次請求後這個帶有pet
名字的屬性值將會自動的存到session中。直到另外一個帶有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
如果想要訪問一個之前存在的session的屬性,可以使用@SessionAttribute
訪問。例如:
@RequestMapping("/")
public String handle(@SessionAttribute User user) {
// ...
}
請他情況下,需要添加或者刪除session的時候,可以通過註入org.springframework.web.context.request.WebRequest
或者javax.servlet.http.HttpSession
實現Session的管理。
@RequestAttribute
和@SessionAttribute
相似,@RequestAttribute
可以訪問請求之前(例如,Filter
,HandlerInterceptor
)創建的請求屬性。例如:
@GetMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}
重定向屬性值
預設情況下,在重定向url中所有的屬性值都通過URI的模版變數暴露。 例如:
@PostMapping("/files/{path}")
public String upload(...) {
// ...
return "redirect:files/{path}";
}
Flash屬性值
Flash屬性值可以保存一個請求的數據使得另一個請求可以使用他的數據。最常用的場景就是重定向,例如:Post/Redirect/Get模式。在重定向之前臨時將Flash屬性保存(一般保存在session中)。這樣在另一個請求中就可以獲取保存值,之後就會被立即刪除。 Spring MVC 通過FlashMap
和FlashMapManager
支持Flash屬性。FlashMap
保存值,FlashMapManager
用來保存,查詢,管理FlashMap
實例。 Flash屬性預設開啟,如果不使用則不會創建HTTP session。對於每個請求來說都有一個input的FlashMap
,包含了上一個請求傳遞的屬性和一個output的FlashMap
包含需要傳遞的屬性。這兩個FlashMap都可以通過RequestContextUtils
中的靜態方法來獲取。 一般來講controller不會直接使用FlashMap
。其方法參數RedirectAttributes
預設情況下使用flash map存儲需要重定向的數據,保存到output的FlashMap
中,重定向後,自動從input的FlashMap中獲取數據添加到Model中。
Multipart
啟用MultipartResolver
後,如果POST請求包含了multipart/form-data
,則其將會解析請求參數,獲取Multipart。下麵是上次文件的示例:
@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";
}
}
如果使用Servlet 3.0 則可以用javax.servlet.http.Part
代替Spring的MultipartFile
。 Multipart 的內容同樣可以作為數據綁定的一部分,例如:
class MyForm {
private String name;
private MultipartFile file;
// ...
}
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(MyForm form, BindingResult errors) {
if (!form.getFile().isEmpty()) {
byte[] bytes = form.getFile().getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
}
return "redirect:uploadFailure";
}
}
Multipart請求同樣可以通過非瀏覽器提交,例如:下麵是一個JSON的示例:
POST /someUrl
Content-Type: multipart/mixed
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit
{
"name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...
可以通過@RequestParam
來獲取元信息,但是更好的做法是使用@RequestPart
來獲取其元信息。例如:
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
@RequestPart("file-data") MultipartFile file) {
// ...
}
@RequestPart
可以和javax.validation.Valid
或者Spring的@Validated
註解一同使用,通過標準的bean驗證來校驗數據的準確性。預設情況下校驗錯誤拋出MethodArgumentNotValidException
的異常,會直接返回404的錯誤。同樣可以通過BindingResult
來自己處理異常情況。
@RequestBody
使用了@RequestBody
的參數通過HttpMessageConverter
來將請求體反序列化成一個對象。下麵是使用@RequestBody
的示例:
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
@RequestBody
同樣可以和javax.validation.Valid
或者Spring的@Validated
註解一同使用。預設拋出的異常是MethodArgumentNotValidException
處理方法同@RequestPart
@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
// ...
}
HttpEntity
HttpEntity
的使用和@RequestBody
相似,不過他可以同時包含header和body。使用方法如下:
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
// ...
}
@ReponseBody
在方法中使用@ResponseBody
修飾,則會自動的將返回值通過HttpMessageConverter
的轉換寫入到響應體中。 使用方法如下:
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}
@ResponseBody
同樣支持類級別,如果修飾controller類,那麼所有的方法都會繼承這個註解。這個和@RestController
一樣,@RestController
就是@Controller
和@RequestBody
的組合。
ResponseEntity
ResponseEntity
同@ResponseBody
相似,只是其同時包含了響應的header和body。使用如下:
@PostMapping("/something")
public ResponseEntity<String> handle() {
// ...
URI location = ... ;
return ResponseEntity.created(location).build();
}
Jackson JSON
Spring MVC 內監了對Jackson序列化視圖的支持。在使用ResponseEntity
和@ResponseBody
的時候可以使用@JsonView來啟動序列化的視圖類。使用如下:
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;
}
}
如果controller的方法返回的是一個字元串的視圖,可以將其放到model中啟用:
@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";
}
}
Jackson JSONP
為了開啟@ResponseBody
和ResonseEntity
的JSONP的支持,可以通過定義一個@ControllerAdvice
的bean繼承AbstractJsonpResponseBodyAdvice
,其預設構造參數就是JSONP的查詢參數,使用如下:
@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpAdvice() {
super("callback");
}
}