前提
在日常使用SpringMVC進行開發的時候,有可能遇到前端各種類型的請求參數,這里做一次相對全面的總結。SpringMVC中處理控制器參數的接口是HandlerMethodArgumentResolver,此接口有眾多子類,分別處理不同(注解類型)的參數,下面只列舉幾個子類:
- RequestParamMethodArgumentResolver:解析處理使用了@RequestParam注解的參數、MultipartFile類型參數和Simple類型(如long、int)參數。
- RequestResponseBodyMethodProcessor:解析處理@RequestBody注解的參數。
- PathVariableMapMethodArgumentResolver:解析處理@PathVariable注解的參數。
實際上,一般在解析一個控制器的請求參數的時候,用到的是HandlerMethodArgumentResolverComposite,里面裝載了所有啟用的HandlerMethodArgumentResolver子類。而HandlerMethodArgumentResolver子類在解析參數的時候使用到HttpMessageConverter(實際上也是一個列表,進行遍歷匹配解析)子類進行匹配解析,常見的如MappingJackson2HttpMessageConverter。而HandlerMethodArgumentResolver子類到底依賴什么HttpMessageConverter實例實際上是由請求頭中的ContentType(在SpringMVC中統一命名為MediaType,見org.springframework.http.MediaType)決定的,因此我們在處理控制器的請求參數之前必須要明確外部請求的ContentType到底是什么。上面的邏輯可以直接看源碼AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters
,思路是比較清晰的。在@RequestMapping注解中,produces和consumes就是和請求或者響應的ContentType相關的:
- consumes:指定處理請求的提交內容類型(ContentType),例如application/json, text/html,只有命中了才會接受該請求。
- produces:指定返回的內容類型,僅當request請求頭中的(Accept)類型中包含該指定類型才返回,如果返回的是JSON數據一般使用application/json;charset=UTF-8。
另外提一點,SpringMVC中默認使用Jackson作為JSON的工具包,如果不是完全理解透整套源碼的運作,一般不是十分建議修改默認使用的MappingJackson2HttpMessageConverter(例如有些人喜歡使用FastJson,實現HttpMessageConverter引入FastJson做轉換器)。
SpringMVC請求參數接收
其實一般的表單或者JSON數據的請求都是相對簡單的,一些復雜的處理主要包括URL路徑參數、文件上傳、數組或者列表類型數據等。另外,關于參數類型中存在日期類型屬性(例如java.util.Date、java.sql.Date、java.time.LocalDate、java.time.LocalDateTime),解析的時候一般需要自定義實現的邏輯實現String->日期類型的轉換。其實道理很簡單,日期相關的類型對于每個國家、每個時區甚至每個使用者來說認知都不一定相同。在演示一些例子主要用到下面的模特類:
@Data
public class User {
private String name;
private Integer age;
private List<Contact> contacts;
}
@Data
public class Contact {
private String name;
private String phone;
}
表單參數
非對象類型單個參數接收:
這種是最常用的表單參數提交,ContentType指定為application/x-www-form-urlencoded,也就是會進行URL編碼。
對應的控制器如下:
@PostMapping(value = "/post")
public String post(@RequestParam(name = "name") String name,
@RequestParam(name = "age") Integer age) {
String content = String.format("name = %s,age = %d", name, age);
log.info(content);
return content;
}
說實話,如果有毅力的話,所有的復雜參數的提交最終都可以轉化為多個單參數接收,不過這樣做會產生十分多冗余的代碼,而且可維護性比較低。這種情況下,用到的參數處理器是RequestParamMapMethodArgumentResolver。
對象類型參數接收:
我們接著寫一個接口用于提交用戶信息,用到的是上面提到的模特類,主要包括用戶姓名、年齡和聯系人信息列表,這個時候,我們目標的控制器最終編碼如下:
@PostMapping(value = "/user")
public User saveUser(User user) {
log.info(user.toString());
return user;
}
我們還是指定ContentType為application/x-www-form-urlencoded,接著我們需要構造請求參數:
因為沒有使用注解,最終的參數處理器為ServletModelAttributeMethodProcessor,主要是把HttpServletRequest中的表單參數封裝到MutablePropertyValues實例中,再通過參數類型實例化(通過構造反射創建User實例),反射匹配屬性進行值的填充。另外,請求復雜參數里面的列表屬性請求參數看起來比較奇葩,實際上和在.properties文件中添加最終映射到Map類型的參數的寫法是一致的。那么,能不能把整個請求參數塞在一個字段中提交呢?
直接這樣做是不行的,因為實際提交的form表單,key是user,value實際上是一個字符串,缺少一個String->User類型的轉換器,實際上RequestParamMethodArgumentResolver依賴WebConversionService中Converter列表進行參數轉換:
解決辦法還是有的,添加一個org.springframework.core.convert.converter.Converter實現即可:
@Component
public class StringUserConverter implements Converter<String, User> {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public User convert(String source) {
try {
return MAPPER.readValue(source, User.class);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
}
上面這種做法屬于曲線救國的做法,不推薦使用在生產環境,但是如果有些第三方接口的對接無法避免這種參數,可以選擇這種實現方式。
JSON參數
一般來說,直接POST一個JSON字符串這種方式對于SpringMVC來說是比較友好的,只需要把ContentType設置為application/json,提交一個原始的JSON字符串即可:
后端控制器的代碼也比較簡單:
@PostMapping(value = "/user-2")
public User saveUser2(@RequestBody User user) {
log.info(user.toString());
return user;
}
因為使用了@RequestBody注解,最終使用到的參數處理器為RequestResponseBodyMethodProcessor,實際上會用到MappingJackson2HttpMessageConverter進行參數類型的轉換,底層依賴到Jackson相關的包。
URL參數
URL參數,或者叫請求路徑參數是基于URL模板獲取到的參數,例如/user/{userId}是一個URL模板(URL模板中的參數占位符是{}),實際請求的URL為/user/1,那么通過匹配實際請求的URL和URL模板就能提取到userId為1。在SpringMVC中,URL模板中的路徑參數叫做PathVariable,對應注解@PathVariable,對應的參數處理器為PathVariableMethodArgumentResolver。注意一點是,@PathVariable的解析是按照value(name)屬性進行匹配,和URL參數的順序是無關的。舉個簡單的例子:
后臺的控制器如下:
@GetMapping(value = "/user/{name}/{age}")
public String findUser1(@PathVariable(value = "age") Integer age,
@PathVariable(value = "name") String name) {
String content = String.format("name = %s,age = %d", name, age);
log.info(content);
return content;
}
這種用法被廣泛使用于Representational State Transfer(REST)的軟件架構風格,個人覺得這種風格是比較靈活和清晰的(從URL和請求方法就能完全理解接口的意義和功能)。下面再介紹兩種相對特殊的使用方式。
帶條件的URL參數
其實路徑參數支持正則表達式,例如我們在使用/sex/{sex}接口的時候,要求sex必須是F(Female)或者M(Male),那么我們的URL模板可以定義為/sex/{sex:M|F},代碼如下:
@GetMapping(value = "/sex/{sex:M|F}")
public String findUser2(@PathVariable(value = "sex") String sex){
log.info(sex);
return sex;
}
只有/sex/F或者/sex/M的請求才會進入findUser2控制器方法,其他該路徑前綴的請求都是非法的,會返回404狀態碼。這里僅僅是介紹了一個最簡單的URL參數正則表達式的使用方式,更強大的用法可以自行摸索。
@MatrixVariable的使用
MatrixVariable也是URL參數的一種,對應注解@MatrixVariable,不過它并不是URL中的一個值(這里的值指定是兩個"/"之間的部分),而是值的一部分,它通過";"進行分隔,通過"="進行K-V設置。說起來有點抽象,舉個例子:假如我們需要打電話給一個名字為doge,性別是男,分組是碼畜的程序員,GET請求的URL可以表示為:/call/doge;gender=male;group=programmer
,我們設計的控制器方法如下:
@GetMapping(value = "/call/{name}")
public String find(@PathVariable(value = "name") String name,
@MatrixVariable(value = "gender") String gender,
@MatrixVariable(value = "group") String group) {
String content = String.format("name = %s,gender = %s,group = %s", name, gender, group);
log.info(content);
return content;
}
當然,如果你按照上面的例子寫好代碼,嘗試請求一下該接口發現是報錯的:400 Bad Request - Missing matrix variable 'gender' for method parameter of type String。這是因為@MatrixVariable注解的使用是不安全的,在SpringMVC中默認是關閉對其支持。要開啟對@MatrixVariable的支持,需要設置RequestMappingHandlerMapping#setRemoveSemicolonContent方法為false:
@Configuration
public class CustomMvcConfiguration implements InitializingBean {
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
@Override
public void afterPropertiesSet() throws Exception {
requestMappingHandlerMapping.setRemoveSemicolonContent(false);
}
}
除非有很特殊的需要,否則不建議使用@MatrixVariable。
文件上傳
文件上傳在使用POSTMAN模擬請求的時候需要選擇form-data,POST方式進行提交:
假設我們在D盤有一個圖片文件叫doge.jpg,現在要通過本地服務接口把文件上傳,控制器的代碼如下:
@PostMapping(value = "/file1")
public String file1(@RequestPart(name = "file1") MultipartFile multipartFile) {
String content = String.format("name = %s,originName = %s,size = %d",
multipartFile.getName(), multipartFile.getOriginalFilename(), multipartFile.getSize());
log.info(content);
return content;
}
控制臺輸出是:
name = file1,originName = doge.jpg,size = 68727
可能有點疑惑,參數是怎么來的,我們可以用Fildder抓個包看下:
可知MultipartFile實例的主要屬性分別來自Content-Disposition、content-type和content-length,另外,InputStream用于讀取請求體的最后部分(文件的字節序列)。參數處理器用到的是RequestPartMethodArgumentResolver(記住一點,使用了@RequestPart和MultipartFile一定是使用此參數處理器)。在其他情況下,使用@RequestParam和MultipartFile或者僅僅使用MultipartFile(參數的名字必須和POST表單中的Content-Disposition描述的name一致)也可以接收上傳的文件數據,主要是通過RequestParamMethodArgumentResolver進行解析處理的,它的功能比較強大,具體可以看其supportsParameter
方法,這兩種情況的控制器方法代碼如下:
@PostMapping(value = "/file2")
public String file2(MultipartFile file1) {
String content = String.format("name = %s,originName = %s,size = %d",
file1.getName(), file1.getOriginalFilename(), file1.getSize());
log.info(content);
return content;
}
@PostMapping(value = "/file3")
public String file3(@RequestParam(name = "file1") MultipartFile multipartFile) {
String content = String.format("name = %s,originName = %s,size = %d",
multipartFile.getName(), multipartFile.getOriginalFilename(), multipartFile.getSize());
log.info(content);
return content;
}
其他參數
其他參數主要包括請求頭、Cookie、Model、Map等相關參數,還有一些并不是很常用或者一些相對原生的屬性值獲取(例如HttpServletRequest、HttpServletResponse等)不做討論。
請求頭
請求頭的值主要通過@RequestHeader注解的參數獲取,參數處理器是RequestHeaderMethodArgumentResolver,需要在注解中指定請求頭的Key。簡單實用如下:
控制器方法代碼:
@PostMapping(value = "/header")
public String header(@RequestHeader(name = "Content-Type") String contentType) {
return contentType;
}
Cookie
Cookie的值主要通過@CookieValue注解的參數獲取,參數處理器為ServletCookieValueMethodArgumentResolver,需要在注解中指定Cookie的Key??刂破鞣椒ùa如下:
@PostMapping(value = "/cookie")
public String cookie(@CookieValue(name = "JSESSIONID") String sessionId) {
return sessionId;
}
Model類型參數
Model類型參數的處理器是ModelMethodProcessor,實際上處理此參數是直接返回ModelAndViewContainer實例中的Model(ModelMap類型),因為要橋接不同的接口和類的功能,因此回調的實例是BindingAwareModelMap類型,此類型繼承自ModelMap同時實現了Model接口。舉個例子:
@GetMapping(value = "/model")
public String model(Model model, ModelMap modelMap) {
log.info("{}", model == modelMap);
return "success";
}
注意調用此接口,控制臺輸出Info日志內容為:true。ModelMap或者Model中添加的屬性項會附加到HttpRequestServlet中帶到頁面中進行渲染。
@ModelAttribute參數
@ModelAttribute注解處理的參數處理器為ModelAttributeMethodProcessor,@ModelAttribute的功能源碼的注釋如下:
Annotation that binds a method parameter or method return value to a named model attribute, exposed to a web view.
簡單來說,就是通過key-value形式綁定方法參數或者方法返回值到Model(Map)中,區別下面三種情況:
- 1、@ModelAttribute使用在方法(返回值)上,方法沒有返回值(void類型), Model(Map)參數需要自行設置。
- 2、@ModelAttribute使用在方法(返回值)上,方法有返回值(非void類型),返回值會添加到Model(Map)參數,key由@ModelAttribute的value指定,否則會使用返回值類型字符串(首寫字母變為小寫)。
- 3、@ModelAttribute使用在方法參數中。
在一個控制器(使用了@Controller)中,如果存在一到多個使用了@ModelAttribute的方法,這些方法總是在進入控制器方法之前執行,并且執行順序是由加載順序決定的(具體的順序是帶參數的優先,并且按照方法首字母升序排序),舉個例子:
@Slf4j
@RestController
public class ModelAttributeController {
@ModelAttribute
public void before(Model model) {
log.info("before..........");
model.addAttribute("before", "beforeValue");
}
@ModelAttribute(value = "beforeArg")
public String beforeArg() {
log.info("beforeArg..........");
return "beforeArgValue";
}
@GetMapping(value = "/modelAttribute")
public String modelAttribute(Model model, @ModelAttribute(value = "beforeArg") String beforeArg) {
log.info("modelAttribute..........");
log.info("beforeArg..........{}", beforeArg);
log.info("{}", model);
return "success";
}
@ModelAttribute
public void after(Model model) {
log.info("after..........");
model.addAttribute("after", "afterValue");
}
@ModelAttribute(value = "afterArg")
public String afterArg() {
log.info("afterArg..........");
return "afterArgValue";
}
}
調用此接口,控制臺輸出日志如下:
after..........
before..........
afterArg..........
beforeArg..........
modelAttribute..........
beforeArg..........beforeArgValue
{after=afterValue, before=beforeValue, afterArg=afterArgValue, beforeArg=beforeArgValue}
可以印證排序規則和參數設置、獲取。
Errors或者BindingResult參數
Errors其實是BindingResult的父接口,BindingResult主要用于回調JSR參數校驗異常的屬性項,如果JSR校驗異常,一般會拋出MethodArgumentNotValidException異常,并且會返回400(Bad Request),見全局異常處理器DefaultHandlerExceptionResolver。Errors類型的參數處理器為ErrorsMethodArgumentResolver。舉個例子:
@PostMapping(value = "/errors")
public String errors(@RequestBody @Validated ErrorsModel errors, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
for (ObjectError objectError : bindingResult.getAllErrors()) {
log.warn("name={},message={}", objectError.getObjectName(), objectError.getDefaultMessage());
}
}
return errors.toString();
}
//ErrorsModel
@Data
@NoArgsConstructor
public class ErrorsModel {
@NotNull(message = "id must not be null!")
private Integer id;
@NotEmpty(message = "errors name must not be empty!")
private String name;
}
調用接口控制臺Warn日志如下:
name=errors,message=errors name must not be empty!
一般情況下,不建議用這種方式處理JSR校驗異常的屬性項,因為會涉及到大量的重復的硬編碼工作,建議直接繼承ResponseEntityExceptionHandler,覆蓋對應的方法。
@Value參數
控制器方法的參數可以是@Value注解修飾的參數,會從Environment中裝配和轉換屬性值到對應的參數中(也就是參數的來源并不是請求體),參數處理器為ExpressionValueMethodArgumentResolver。舉個例子:
@GetMapping(value = "/value")
public String value(@Value(value = "${spring.application.name}") String name) {
log.info("spring.application.name={}", name);
return name;
}
Map類型參數
Map類型參數的范圍相對比較廣,對應一系列的參數處理器,注意區別使用了上面提到的部分注解的Map類型和完全不使用注解的Map類型參數,兩者的處理方式不相同。下面列舉幾個相對典型的Map類型參數處理例子。
不使用任何注解的Map<String,Object>參數
這種情況下參數實際上直接回調ModelAndViewContainer中的ModelMap實例,參數處理器為MapMethodProcessor,往Map參數中添加的屬性將會帶到頁面中。
使用@RequestParam注解的Map<String,Object>參數
這種情況下的參數處理器為RequestParamMapMethodArgumentResolver,使用的請求方式需要指定ContentType為x-www-form-urlencoded,不能使用application/json的方式:
控制器代碼為:
@PostMapping(value = "/map")
public String mapArgs(@RequestParam Map<String, Object> map) {
log.info("{}", map);
return map.toString();
}
使用@RequestHeader注解的Map<String,Object>參數
這種情況下的參數處理器為RequestHeaderMapMethodArgumentResolver,作用是獲取請求的所有請求頭的Key-Value。
使用@PathVariable注解的Map<String,Object>參數
這種情況下的參數處理器為PathVariableMapMethodArgumentResolver,作用是獲取所有路徑參數封裝為Key-Value結構。
MultipartFile集合-批量文件上傳
批量文件上傳的時候,我們一般需要接收一個MultipartFile集合,可以有兩種選擇:
- 1、使用MultipartHttpServletRequest參數,直接調用
getFiles
方法獲取MultipartFile列表。 - 2、使用@RequestParam注解修飾MultipartFile列表,參數處理器是RequestParamMethodArgumentResolver,其實就是第一種的封裝而已。
控制器方法代碼如下:
@PostMapping(value = "/parts")
public String partArgs(@RequestParam(name = "file") List<MultipartFile> parts) {
log.info("{}", parts);
return parts.toString();
}
日期類型參數處理
日期處理個人認為是請求參數處理中最復雜的,因為一般日期處理的邏輯不是通用的,過多的定制化處理導致很難有一個統一的標準處理邏輯去處理和轉換日期類型的參數。不過,這里介紹幾個通用的方法,以應對各種奇葩的日期格式。下面介紹的例子中全部使用Jdk8中引入的日期時間API,圍繞java.util.Date為核心的日期時間API的使用方式類同。
一、統一以字符串形式接收
這種是最原始但是最奏效的方式,統一以字符串形式接收,然后自行處理類型轉換,下面給個小例子:
@PostMapping(value = "/date1")
public String date1(@RequestBody UserDto userDto) {
UserEntity userEntity = new UserEntity();
userEntity.setUserId(userDto.getUserId());
userEntity.setBirthdayTime(LocalDateTime.parse(userDto.getBirthdayTime(), FORMATTER));
userEntity.setGraduationTime(LocalDateTime.parse(userDto.getGraduationTime(), FORMATTER));
log.info(userEntity.toString());
return "success";
}
@Data
public class UserDto {
private String userId;
private String birthdayTime;
private String graduationTime;
}
@Data
public class UserEntity {
private String userId;
private LocalDateTime birthdayTime;
private LocalDateTime graduationTime;
}
二、使用注解@DateTimeFormat或者@JsonFormat
@DateTimeFormat注解配合@RequestBody的參數使用的時候,會發現拋出InvalidFormatException異常,提示轉換失敗,這是因為在處理此注解的時候,只支持form提交(ContentType為x-www-form-urlencoded),例子如下:
@Data
public class UserDto2 {
private String userId;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime birthdayTime;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime graduationTime;
}
@PostMapping(value = "/date2")
public String date2(UserDto2 userDto2) {
log.info(userDto2.toString());
return "success";
}
//或者像下面這樣
@PostMapping(value = "/date2")
public String date2(@RequestParam("name"="userId")String userId,
@RequestParam("name"="birthdayTime")LocalDateTime birthdayTime,
@RequestParam("name"="graduationTime")LocalDateTime graduationTime) {
return "success";
}
而@JsonFormat注解可使用在form或者Json請求參數的場景,因此更推薦使用@JsonFormat注解,不過注意需要指定時區(timezone屬性,例如在中國是東八區"GMT+8"),否則有可能導致出現"時差",舉個例子:
@PostMapping(value = "/date2")
public String date2(@RequestBody UserDto2 userDto2) {
log.info(userDto2.toString());
return "success";
}
@Data
public class UserDto2 {
private String userId;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime birthdayTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime graduationTime;
}
三、Jackson序列化和反序列化定制
因為SpringMVC默認使用Jackson處理@RequestBody的參數轉換,因此可以通過定制序列化器和反序列化器來實現日期類型的轉換,這樣我們就可以使用application/json的形式提交請求參數。這里的例子是轉換請求Json參數中的字符串為LocalDateTime類型,屬于Json反序列化,因此需要定制反序列化器:
@PostMapping(value = "/date3")
public String date3(@RequestBody UserDto3 userDto3) {
log.info(userDto3.toString());
return "success";
}
@Data
public class UserDto3 {
private String userId;
@JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
private LocalDateTime birthdayTime;
@JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
private LocalDateTime graduationTime;
}
public class CustomLocalDateTimeDeserializer extends LocalDateTimeDeserializer {
public CustomLocalDateTimeDeserializer() {
super(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
}
四、最佳實踐
前面三種方式都存在硬編碼等問題,其實最佳實踐是直接修改MappingJackson2HttpMessageConverter中的ObjectMapper對于日期類型處理默認的序列化器和反序列化器,這樣就能全局生效,不需要再使用其他注解或者定制序列化方案(當然,有些時候需要特殊處理定制),或者說,在需要特殊處理的場景才使用其他注解或者定制序列化方案。使用鉤子接口Jackson2ObjectMapperBuilderCustomizer可以實現ObjectMapper的屬性定制:
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
return customizer->{
customizer.serializerByType(LocalDateTime.class,new LocalDateTimeSerializer(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
customizer.deserializerByType(LocalDateTime.class,new LocalDateTimeDeserializer(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
};
}
這樣就能定制化MappingJackson2HttpMessageConverter中持有的ObjectMapper,上面的LocalDateTime序列化和反序列化器對全局生效。
請求URL匹配
前面基本介紹完了主流的請求參數處理,其實SpringMVC中還會按照URL的模式進行匹配,使用的是Ant路徑風格,處理工具類為org.springframework.util.AntPathMatcher
,從此類的注釋來看,匹配規則主要包括下面四點:
- 1、
?
匹配1個字符。 - 2、
*
匹配0個或者多個字符。 - 3、
**
匹配路徑中0個或者多個目錄。 - 4、
{spring:[a-z]+}
將正則表達式[a-z]+匹配到的值,賦值給名為spring的路徑變量。
舉些例子:
?形式的URL:
@GetMapping(value = "/pattern?")
public String pattern() {
return "success";
}
/pattern 404 Not Found
/patternd 200 OK
/patterndd 404 Not Found
/pattern/ 404 Not Found
/patternd/s 404 Not Found
*形式的URL:
@GetMapping(value = "/pattern*")
public String pattern() {
return "success";
}
/pattern 200 OK
/pattern/ 200 OK
/patternd 200 OK
/pattern/a 404 Not Found
**形式的URL:
@GetMapping(value = "/pattern/**/p")
public String pattern() {
return "success";
}
/pattern/p 200 OK
/pattern/x/p 200 OK
/pattern/x/y/p 200 OK
{spring:[a-z]+}形式的URL:
@GetMapping(value = "/pattern/{key:[a-c]+}")
public String pattern(@PathVariable(name = "key") String key) {
return "success";
}
/pattern/a 200 OK
/pattern/ab 200 OK
/pattern/abc 200 OK
/pattern 404 Not Found
/pattern/abcd 404 Not Found
上面的四種URL模式可以組合使用,千變萬化。
URL匹配還遵循精確匹配原則,也就是存在兩個模式對同一個URL都能夠匹配成功,則選取最精確的URL匹配,進入對應的控制器方法,舉個例子:
@GetMapping(value = "/pattern/**/p")
public String pattern1() {
return "success";
}
@GetMapping(value = "/pattern/p")
public String pattern2() {
return "success";
}
上面兩個控制器,如果請求URL為/pattern/p,最終進入的方法為pattern2
。
最后,org.springframework.util.AntPathMatcher
作為一個工具類,可以單獨使用,不僅僅可以用于匹配URL,也可以用于匹配系統文件路徑,不過需要使用其帶參數構造改變內部的pathSeparator變量,例如:
AntPathMatcher antPathMatcher = new AntPathMatcher(File.separator);
小結
筆者在前一段時間曾經花大量時間梳理和分析過Spring、SpringMVC的源碼,但是后面一段很長的時間需要進行業務開發,對架構方面的東西有點生疏了,畢竟東西不用就會生疏,這個是常理。這篇文章基于一些SpringMVC的源碼經驗總結了請求參數的處理相關的一些知識,希望幫到自己和大家。
參考資料:
- spring-boot-web-starter:2.0.3.RELEASE源碼。
(本文完)