前言
接上篇《深入理解 Spring MVC Controller —— 请求映射》,上篇主要介绍了处理器方法及请求映射的定义。有了处理器方法 Spring MVC 就可以对请求进行处理,有了请求映射 Spring MVC 就能知道哪些请求应该由哪些处理器方法来处理。
Spring MVC 中对请求进行处理,除了定义处理器方法以及请求映射,我们还需要关心的一个问题是如何接收请求有关的参数,因为不同的参数往往伴随着不同的业务逻辑。
由于请求参数获取的内容较多,这篇我们先来了解 Spring MVC 常见的参数获取方式,下篇我们再深入分析并实战如何利用请求参数优雅获取登录用户信息。
前方高能预警:作为基础内容,本篇篇幅略长,可先收藏等有时间再查阅。
请求参数获取
传统的 Java Web 项目,我们通常通过 HttpServletRequest 来获取请求相关的参数。Spring MVC 简化了请求参数的获取方式,直接将请求参数定义为处理器方法参数即可。
当处理器方法被 Spring MVC 调用时,Spring MVC 会尝试根据请求上下文信息解析出给定类型的方法参数值,并自动进行类型转换和参数校验,因此方法参数还可以定义为除字符串类型的其他类型。默认情况下处理器方法中可以定义的请求参数如下。
表单参数
表单参数可能是在 Spring MVC 中使用最频繁的请求参数之一了。
Http 协议中的表单大概可以分为两类,一类是 application/x-www-from-urlencoded,请求中只能携带字符串形式的参数值,另一类是 multipart/form-data,请求中除了携带字符串形式的参数值还可以携带文件。
Spring MVC 对字符串参数值和文件参数值有不同的处理方式。
表单单个普通参数获取
@RequestParam 注解指定请求参数
对于表单的单个普通参数获取,可以直接在处理器方法参数上添加 @RequestParam 注解并指定参数名,接收的是 GET 请求方式查询字符串上的参数或 POST 请求方式表单上的某一个参数。
Spring MVC 并未限制 @RequestParam 标注的处理器方法参数的类型,只要内部支持类型转换即可,方法参数类型通常来说为简单类型。简单类型包括基本类型及其包装类型、Enum、CharSequence、Number、Date、Temporal、URI、URL、Locale、Class 类型。
示例代码如下。
@PostMapping("/login") public Result<LoginBO> login(@RequestParam("username") String username, @RequestParam("password") String password) { return Result.ok(userService.login(username, password)); }
示例代码提供了登录功能,并获取了用户名和密码。
如果 JDK 版本在 1.8 以上使用 javac 编译时还可以添加 -parameters 参数,这样 @RequestParam 可以省略参数名,Spring MVC 会根据方法参数名获取参数值。
注意:如果想使用 Map 作为方法类型接收单个参数值,@RequestParam 必须指定参数名,否则 Map 方法参数接收的是所有的表单参数。
简单类型直接接收请求参数
类型为简单类型的处理器方法参数可以直接接收表单中的单个参数,方法参数名就是接收的参数名,注意此时方法参数上不能存在 @RequestParam 注解及 @RequestPart 注解。
表单单个文件参数获取
@RequestPart 指定文件参数
如果想要接收用户上传的文件,可以将 Multipart 相关类型直接定义为处理器方法参数的类型,然后使用 @RequestPart 指定请求参数名。
Multipart 相关的参数类型如下:
MultipartFile、Collection、List、MultipartFile[] 类型参数。
Part、Collection、List、Part[] 类型参数。
示例代码如下。
@PostMapping("/upload") public Result<UploadBO> upload(@RequestPart("file") MultipartFile file) { return Result.ok(fileService.upload(file)); }
Multipart 相关类型直接接收文件参数
除了使用 @RequestPart 注解指定参数名,还可以直接将 Multipart 相关类型直接定义为处理器方法参数,方法参数名就是请求参数名,注意此时方法参数上不能携带 @RequestParam 注解及 @RequestPart 注解。
所有表单参数获取
如果想要一次性获取所有的请求参数,而不是根据请求参数名获取,可以将处理器方法参数定义为 Map 或 MultiValueMap,然后使用 @RequestParam 注解标注方法参数,此时 @RequestParam 不能指定参数名。
具体可以使用的泛型类型如下。
MultiValueMap、MultiValueMap、MultiValueMap
Map、Map、Map
由于泛型的值只能为 String、MultipartFile、Part,因此普通表单参数和文件表单参数需要分别接收。
示例代码如下。
@PostMapping("/upload") public Result<UploadBO> upload(@RequestParam Map<String, MultipartFile> files, @RequestParam Map<String, String> extra) { return null; }
可选请求参数
如果认为方法参数表示的表单请求参数是可选的,可以将 @RequestParam
的 required
属性值指定为 false,表示该参数是可选的,除此之外还可以用 Optional
类型接收请求参数。示例代码如下。
@PostMapping("/login") public Result<LoginBO> login(@RequestParam(value = "username", required = false) String username, @RequestParam("password") Optional<String> password) { return Result.ok(userService.login(username, password)); }
请求头参数
Spring MVC 中,如果想要获取请求头中的单个参数值,可以使用 @RequestHeader
指定请求头,并标注在处理器方法参数上。与 @RequestParam
注解类似,请求头的名称也可以省略,此时将方法参数作为请求头的名称。示例代码如下。
@PostMapping("/upload") public Result<UploadBO> upload(@RequestHeader("token") String token) { return null; }
如果想要一次性获取所有的请求头,可以使用 Map 类型作为处理器方法参数,Map 的 key 和 value 都为 String 类型,此时 @RequestHeader
注解不必指定请求头,示例代码如下。
@PostMapping("/upload") public Result<UploadBO> upload(@RequestHeader Map<String, String> header1,@RequestHeader MultiValueMap<String, String> header2) { return null; }
默认情况 @RequestHeader 表示的请求头必须存在,如果是可选的,可以设置 @RequestHeader(required = false) 或使用 Optional 类型接收请求头。
Cookie 参数
请求头中可能包含多个 cookie 信息,如果直接获取 Cookie 请求头的值然后再解析会比较麻烦。
Spring MVC 支持使用 String 类型或 Cookie 类型作为处理器方法参数类型来接收 Cookie信息,在方法参数上添加 @CookieValue 注解指定 Cookie 的名称即可。
如果省略 Cookie 名称则方法参数名作为 Cookie 名称。示例代码如下。
@PostMapping("/upload") public Result<UploadBO> upload(@CookieValue("username") String username, @CookieValue Cookie password) { return null; }
默认情况 @CookieValue 表示的 Cookie 必须存在,如果是可选的,可以设置 @CookieValue(required = false) 或使用 Optional 类型接收 Cookie。
路径变量
路径变量常用于 RESTFUL 风格的接口中,路径的值可以作为一个参数值,此时我们需要在映射路径中指定路径变量的名称,然后使用处理器方法参数接收。处理器方法参数上添加 @PathVariable 注解指定路径变量名即可,同样变量名也可以忽略,此时方法参数名作为路径变量名。示例代码如下。
@GetMapping("/user/{userId}") public Result<?> getUser(@PathVariable("userId") String userId) { return null; }
默认情况 @PathVariable 表示的路径变量必须存在,如果是可选的,可以设置 @PathVariable(required = false) 或使用 Optional 类型接收路径变量。
矩阵变量
矩阵变量和路径变量类似,变量同样定义在路径中,但是使用较少。可以使用@MatrixVariable 注解标注的处理器方法参数接收矩阵变量,方法参数类型可以为简单类型,也可以为 Map。
Spring 5.2 版本及以下需要手动开启矩阵变量支持,配置如下。
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper(); urlPathHelper.setRemoveSemicolonContent(false); configurer.setUrlPathHelper(urlPathHelper); } }
处理器方法参数类型为 Map 的情况,@MatrixVariable
如果指定了 name,则 Map 接收的是单个矩阵变量的值。处理器方法参数获取单个矩阵变量值的示例代码如下。
@GetMapping("/user{basic}/info") public Result<?> getUser(@MatrixVariable(name = "basic") String basic) { return Result.ok(basic); }
对于请求路径 /user;basic=zhangsan;/info,处理器方法参数 basic 接收到的值为 zhangsan。
处理器方法参数类型为 Map 的情况,@MatrixVariable 如果没有指定 name,可以使用 pathVar 指定请求映射路径中的变量名, 此时 Map 接收的是多个矩阵变量。处理器方法参数获取多个矩阵变量的示例代码如下。
@GetMapping("/user{basic}/info") public Result<?> getUser(@MatrixVariable(pathVar = "basic") Map<String, String> basic) { return Result.ok(basic); }
对于请求路径 /user;name=zhangsan;age=20/info ,处理器方法参数 basic 接收到的值为 { "name": "zhangsan", "age": "20" }。
默认情况 @MatrixVariable 表示的矩阵变量必须存在,如果是可选的,可以设置 @MatrixVariable(required = false) 或使用 Optional 类型接收矩阵变量。
请求体参数
对于请求体,可以使用 @RequestBody 标注的处理器方法参数接收。示例代码如下。
@Data public class User { private Long id; private String name; } @RestController public class UserController { @GetMapping("/user/update") public Result<?> updateUser(@RequestBody User user) { return null; } }
默认情况 @RequestBody 表示的请求头必须存在,如果是可选的,可以设置 @RequestBody(required = false) 或使用 Optional 类型接收请求体。
其他处理器方法参数
除了用于接收请求参数的处理器方法参数,Spring MVC 还支持定义一些其他的处理器方法参数。这里简单做一些介绍。
Request 相关参数
Request 相关参数表示 request 本身或其基本信息。
Spring MVC 支持的与 request 有关的处理器方法参数类型包括:
WebRequest、ServletRequest、MultipartRequest、HttpSession、PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId。
HttpEntity、RequestEntity。
SessionStatus。
除了上面的参数,Spring MVC 还支持获取 request attribute 中的参数,使用 @RequestAttribute 标注处理器方法参数即可,这可以获取过滤器或拦截中使用 ServletRequest#setAttribute 设置的参数。
Response 相关参数
resposne 相关参数表示 response 本身或其基本信息。
Spring MVC 支持的处理器方法参数类型包括: ServletResponse、OutputStream、Writer。
Model 相关参数
Model 是 MVC 中的概念,是 View 使用的数据,在 Spring MVC 中,不同的 View 实现,Spring 可能会将 Model 数据存至不同的地方,对于 JSP 页面来说,Spring MVC 就把 Model 数据存到了 request attribute 中。
Spring MVC 中可以使用 Model 或 Map 类型接收 model 参数,然后将数据存至 model 中即可。示例代码如下。
@GetMapping("/user/update") public Result<?> updateUser(Model model1, Map<String, Object> model2) { return null; }
在处理器方法执行前,Spring MVC 还会先执行 controller advice bean 和当前 controller 标注了 @ModelAttribute 注解的方法,并将返回值存入 Model 中,由于目前 Model 使用较少,不再加以赘述。
校验相关参数
Spring MVC 中如果想对请求参数进行校验,直接在处理器方法参数上添加 @Validated 注解即可,校验结果则可以定义一个类型为 Errors 的处理器方法参数接收。更多参数校验内容,可以参考我前面文章《Spring 参数校验最佳实践及原理解析》。
这里给出一个简单的示例代码。
@Data public class User { @NotNull(message = "id不能为空") private Long id; @NotBlank(message = "名称不能为空") private String name; } @RestController public class UserController { @GetMapping("/user/update") public Result<?> updateUser(@RequestBody @Validated User user, Errors errors) { if(errors.hasErrors()){ return Result.fail("参数校验失败"); } return null; } }
表达式参数
Spring MVC 还支持 @Value
标注的注解获取环境变量或属性文件中的属性,可以使用表达式指定默认值或环境变量的名称。示例代码如下。
@GetMapping("/environment") public Result<?> updateUser(@Value("${server.port}") String port) { return Result.ok(port); }
总结
Spring MVC 处理器方法由于未限制方法签名,因此较为灵活,Spring MVC 默认情况下支持了不同的处理器方法参数类型,有的用于获取请求参数,有的则用于其他功能。
为了支持良好的扩展性,Spring 内部其实是通过组合模式来实现的,Spring 通过 HandlerMethodArgumentResolver 接口解析处理器方法参数,默认的实现及能处理的方法参数我这里做了一个总结,见下图。欢迎留言交流。