天天看點

SpringMVC請求參數接收總結

前提

在日常使用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。控制器方法代碼如下:

@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") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime birthdayTime,
                    @RequestParam("name"="graduationTime") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 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源碼。

(本文完)

技術公衆号(《Throwable文摘》),不定期推送筆者原創技術文章(絕不抄襲或者轉載):

SpringMVC請求參數接收總結

繼續閱讀