2.5W字简单总结SpringMVC请求参数接收

前提#

在日常使用SpringMVC进行开发的时候,有可能遇到前端各种类型的请求参数,这里做一次相对全面的总结。SpringMVC中处理控制器参数的接口是HandlerMethodArgumentResolver,此接口有众多子类,分别处理不同(注解类型)的参数,下面只列举几个子类:

  • RequestParamMethodArgumentResolver:解析处理使用了@RequestParam注解的参数、MultipartFile类型参数和Simple类型(如longint等类型)参数。
  • RequestResponseBodyMethodProcessor:解析处理@RequestBody注解的参数。
  • PathVariableMapMethodArgumentResolver:解析处理@PathVariable注解的参数。

实际上,一般在解析一个控制器的请求参数的时候,用到的是HandlerMethodArgumentResolverComposite,里面装载了所有启用的HandlerMethodArgumentResolver子类。而HandlerMethodArgumentResolver子类在解析参数的时候使用到HttpMessageConverter(实际上也是一个列表,进行遍历匹配解析)子类进行匹配解析,常见的如MappingJackson2HttpMessageConverter(使用Jackson进行序列化和反序列化)。

HandlerMethodArgumentResolver子类到底依赖什么HttpMessageConverter实例实际上是由请求头中的Content-Type(在SpringMVC中统一命名为MediaType,见org.springframework.http.MediaType)决定的,因此我们在处理控制器的请求参数之前必须要明确外部请求的Content-Type到底是什么。上面的逻辑可以直接看源码AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters,思路是比较清晰的。在@RequestMapping注解中,producesconsumes属性就是和请求的Accept或者响应的Content-Type相关的:

  • consumes属性:指定处理请求的提交内容类型(Content-Type),例如application/jsontext/html等等,只有命中了对应的Content-Type的值才会接受该请求。
  • produces属性:指定返回的内容类型,仅当某个请求的请求头中的(Accept)类型中包含该指定类型才返回,如果返回的是JSON数据一般考虑使用application/json;charset=UTF-8

另外提一点,SpringMVC中默认使用Jackson作为JSON的工具包,如果不是完全理解透整套源码的运作,一般不是十分建议修改默认使用的MappingJackson2HttpMessageConverter(例如有些人喜欢使用FastJson,实现HttpMessageConverter引入FastJsonHTTP消息转换器,其实这种做法并不推荐)。

SpringMVC请求参数接收#

其实一般的表单或者JSON数据的请求都是相对简单的,一些复杂的处理主要包括URL路径参数、文件上传、数组或者列表类型数据等。另外,关于参数类型中存在日期类型属性(例如java.util.Datejava.sql.Datejava.time.LocalDatejava.time.LocalDateTimejava.time.ZonedDateTime等等),解析的时候一般需要自定义实现的逻辑实现String-->日期类型的转换。其实道理很简单,日期相关的类型对于每个国家、每个时区甚至每个使用者来说认知都不一定相同,所以SpringMVC并没有对于日期时间类型的解析提供一个通用的解决方案。在演示一些例子可能用到下面的模特类:

@Data
public class User {

    private String name;
    private Integer age;
    private List<Contact> contacts;
}

@Data
public class Contact {

    private String name;
    private String phone;
}

下面主要以HTTPGET方法和POST方法提交在SpringMVC体系中正确处理参数的例子进行分析,还会花精力整理SpringMVC体系中独有的URL路径参数处理的一些技巧以及最常见的日期参数处理的合理实践(对于GET方法和POST方法提交的参数处理,基本囊括了其他如DELETEPUT等方法的参数处理,随机应变即可)。

GET方法请求参数处理#

HTTP(s)协议使用GET方法进行请求的时候,提交的参数位于URL模式的Query部分,也就是URL?标识符之后的参数,格式是key1=value1&key2=value2GET方法请求参数可以有多种方法获取:

  1. 使用@RequestParam注解处理。
  2. 使用对象接收,注意对象的属性名称要和Query中的参数名称一致。
  3. 使用HttpServletRequest实例提供的方法(不推荐,存在硬编码)。

假设请求的URLhttp://localhost:8080/get?name=doge&age=26,那么控制器如下:

@Slf4j
@RestController
public class SampleController {

    @GetMapping(path = "/get1")
    public void get1(@RequestParam(name = "name") String name,
                     @RequestParam(name = "age") Integer age) {
        log.info("name:{},age:{}", name, age);
    }

    @GetMapping(path = "/get2")
    public void get2(UserVo vo) {
        log.info("name:{},age:{}", vo.getName(), vo.getAge());
    }

    @GetMapping(path = "/get3")
    public void get3(HttpServletRequest request) {
        String name = request.getParameter("name");
        String age = request.getParameter("age");
        log.info("name:{},age:{}", name, age);
    }

    @Data
    public static class UserVo {

        private String name;
        private Integer age;
    }
}

表单参数#

表单参数,一般对应于页面上<form>标签内的所有<input>标签的name-value聚合而成的参数,一般Content-Type指定为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;
}

加入强行指定Content-Typeapplication/x-www-form-urlencoded,需要构造请求参数格式如下:

因为没有使用注解,最终的参数处理器为ServletModelAttributeMethodProcessor,主要是把HttpServletRequest中的表单参数封装到MutablePropertyValues实例中,再通过参数类型实例化(通过构造反射创建User实例),反射匹配属性进行值的填充。另外,请求复杂参数里面的列表属性请求参数看起来比较奇葩,实际上和在.properties文件中添加最终映射到Map类型的参数的写法是一致的,所以对于嵌套数组或者列表类型的第一层索引要写成firstLevel[index].fieldName的形式。那么,能不能把整个请求参数塞在一个字段中提交呢?

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倾听铃的声

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值