本篇介绍下Controller参数注解@RequestParam的使用方法,使用时的注意事项,以及与HttpServletRequest#getParameter方法的区别;
1. 注解@RequestParam的作用
(1)是SpringMVC中接收普通参数的注解,注解打在Controller的方法参数上;
(2)可以将URL请求参数映射绑定到Controller的方法参数上,便于业务接收处理HTTP请求参数;
(3)通过注解的属性,可以对Controller参数做一些简单的"必传校验"和"默认值填充";
2. 注解@RequestParam参数说明
参考:org.springframework.web.bind.annotation.RequestParam
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
/**
* Alias for {@link #name}.
*/
@AliasFor("name")
String value() default "";
/**
* The name of the request parameter to bind to.
* @since 4.2
*/
@AliasFor("value")
String name() default "";
/**
* Whether the parameter is required.
* <p>Defaults to {@code true}, leading to an exception being thrown
* if the parameter is missing in the request. Switch this to
* {@code false} if you prefer a {@code null} value if the parameter is
* not present in the request.
* <p>Alternatively, provide a {@link #defaultValue}, which implicitly
* sets this flag to {@code false}.
*/
boolean required() default true;
/**
* The default value to use as a fallback when the request parameter is
* not provided or has an empty value.
* <p>Supplying a default value implicitly sets {@link #required} to
* {@code false}.
*/
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
解释:
- value:name属性的别名,作用相同;
- name:URL中的参数名,映射到当前注解对应的参数上;如果没有设置该属性,方法参数名则和接口参数名匹配;
- required:默认值是"true",表示该请求路径中必须包含该参数,如果不包含就会报404错误码;
- defaultValue:String类型,默认参数值,如果设置了该值,属性required=true将失效,自动为false;如果没有传该参数,就使用默认值;默认值也可以是SpEL表达式;
3. 使用示例
(1)name属性
@RequestMapping(value = "/testParams", method = RequestMethod.POST)
public BaseResponse<String> testParams(HttpServletRequest request, @RequestParam(name = "pkgName") String packageName) {
log.warn("[request.getParameterMap={} request.getParameter(\"packageName\")={} packageName={}]",
JSON.toJSONString(request.getParameterMap()),
request.getParameter("packageName"),
packageName);
return BaseResponse.success("ok");
}
控制台输出:
[request.getParameterMap={"pkgName":["com.ee.test.aaa"]} request.getParameter("pkgName")=com.ee.test.aaa packageName=com.ee.test.aaa]
URL中的参数名为"pkgName",通过注解name属性,Controller方法参数使用"packageName"去接收参数;
(2)required属性
默认require=true,不传递该参数则会报错;
控制台MissingServletRequestParameterException异常:
org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'pkgName' is not present
require=false,可以不用传递该参数;
@RequestMapping(value = "/testParams", method = RequestMethod.POST)
public BaseResponse<String> testParams(HttpServletRequest request, @RequestParam(name = "pkgName", required = false) String packageName) {
log.warn("[request.getParameterMap={} request.getParameter(\"pkgName\")={} packageName={}]",
JSON.toJSONString(request.getParameterMap()),
request.getParameter("pkgName"),
packageName);
return BaseResponse.success("ok");
}
(3)defaultValue属性
如果设置了该值,属性required=true将失效,URL未传该参数时,会使用默认值;
@RequestMapping(value = "/testParams", method = RequestMethod.POST)
public BaseResponse<String> testParams(HttpServletRequest request,
@RequestParam(name = "pkgName", defaultValue = "defaultPkgName") String packageName) {
log.warn("[request.getParameterMap={} request.getParameter(\"pkgName\")={} packageName={}]",
JSON.toJSONString(request.getParameterMap()),
request.getParameter("pkgName"),
packageName);
return BaseResponse.success("ok");
}
控制台输出:
[request.getParameterMap={} request.getParameter("pkgName")=null packageName=defaultPkgName]
类型转换,这里defaultValue为String类型,参数类型为Integer,将defaultValue置位100,可以看到简单类型可以自动转换:
@RequestMapping(value = "/testParams1", method = RequestMethod.POST)
public BaseResponse<String> testParams1(HttpServletRequest request,
@RequestParam(name = "appId", defaultValue = "100") Integer id) {
log.warn("[request.getParameterMap={} request.getParameter(\"appId\")={} id={}]",
JSON.toJSONString(request.getParameterMap()),
request.getParameter("appId"),
id);
return BaseResponse.success("ok");
}
[request.getParameterMap={} request.getParameter("appId")=null id=100]
如果Controller参数类型为Integer而默认值不是数值类型对应的字符串,请求时会报错方法参数错误:
@RequestMapping(value = "/testParams2", method = RequestMethod.POST)
public BaseResponse<String> testParams2(HttpServletRequest request,
@RequestParam(name = "appId", defaultValue = "defaultAppId") Integer id) {
log.warn("[request.getParameterMap={} request.getParameter(\"appId\")={} id={}]",
JSON.toJSONString(request.getParameterMap()),
request.getParameter("appId"),
id);
return BaseResponse.success("ok");
}
MethodArgumentTypeMismatchException, bad param type: bad params type: id required type java.lang.Integer
4. 也可以不使用@RequestParam注解——注解加与不加的区别
不使用@RequestParam注解,URL参数依然可以接收到;
// 不加注解接收参数
@RequestMapping("/test1")
public String test1(int userId) {
return "list";
}
// 加注解接收参数
@RequestMapping("/test2")
public String test2(@RequestParam int userId) {
return "list";
}
表现为:
(1)不加@RequestParam时,请求的参数名需要和Controller的方法参数名保持一致才能生效;使用@RequestParam时,可以通过@RequestParam(“userId”)或者@RequestParam(value = “userId”)指定传入的参数名;
(2)不加@RequestParam时,参数为非必传,加@RequestParam而不指定注解属性require=false时,参数为必传;并且可以通过@RequestParam(defaultValue = “0”)指定参数默认值;
5. 注解@RequestParam与HttpServletRequest#getParameter方法的区别
request.getParameter("name")和@RequestParam String name的获取方式是不同的,对比如下:
HttpServletRequest#getParameter
(1) request.getParameter(String name):获取name对应的value,如果有多个,返回第一个;
(2) request.getParameterNames():获取request里所有的name,返回Enumeration类型;
(3) request.getParameterValues(String name):获取name对应的所有value;
其实这三种方式都是从一个全局变量Map<String, String[]> 中获取的值,由此可见:key是String类型,也就是name,而value是String数组类型,请求时是可以提供多个相同的key,value值进行请求,下面是个示例;
请求参数相同参数名,传入两个不同的值:
控制台结果:
[request.getParameterMap={"pkgName":["AAA","BBB"]} request.getParameter("pkgName")=AAA packageName=AAA,BBB]
可见:
- Map中是全部的参数K/V,V为数组类型,当相同参数传递多个值时,加入数组;
- request.getParameter会取数组中的第一个值,这里就是AAA;
- Controller方法参数packageName接收到的是数组中的多个值拼接后的结果,这里就是=AAA,BBB;
从上面的结果来看,至少可以知道,@RequestParam与request.getParameter的逻辑是不一样的,来断点看一下:
答案找到了,在这里完成字符串的拼接;
从源码来看,@RequestParam与request.getParameter的逻辑是不一样的,实际调用的是getParameterValues方法,该方法返回的是String数组,并且源码中做了判断,在数组不为null的情况下,如果数组长度为1返回第一个元素,如果否则(大于1)返回当前数组,并通过Spring包的StringUtils将数组转成字符串,用逗号拼接;
结论:request.getParameter和@RequestParam本质上是不同的,所以在不确定是否有多个value的情况下,@RequestParam是不能代替request.getParameter来使用的!
规范的使用@RequestParam写法应该是@RequestParam String[] packageNames,如下:
而request.getParameter获取的确确实实是第一个value,可能我们研发过程中很少遇到多个相同key提交请求这种情况,但是这种情况是真实存在的,如果不了解这段代码,还是把@RequestParam当作获取第一个参数使用,早晚会出问题!
参考: