Spring MVC 中经常会用到 @RequestBody 和 @RequestParam 两个注解来获取请求的参数,那么这两个参数到底有什么区别呢?
首先我们要知道 GET 请求与 POST 请求的区别
GET 请求与 POST 请求的区别
- GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,参数之间以&相连,如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0%E5%A5%BD。如果数据是英文字母或数字,则原样发送;如果是空格,转换为+;如果是中文或其他字符,则直接把字符串用BASE64加密,得出如:%E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII码值。而与之对应的,POST把提交的数据放置在HTTP包的包体中。
- POST的安全性要比GET的安全性高。注意:这里所说的安全性和上面GET提到的“安全”不是同个概念。上面“安全”的含义仅仅是不作数据修改,而这里安全的含义是真正的Security的含义。比如:通过GET提交数据,用户名和密码将明文出现在URL上,因为:(1)登录页面有可能被浏览器缓存,(2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用GET提交数据还可能会造成Cross-site request forgery攻击(CSRF,跨站请求伪造,也被称为:one click attack/session riding)。
参考:浅谈HTTP中GET、POST用法以及它们的区别
那么在 Spring MVC 中获取 GET 请求与 POST 请求的参数有什么区别呢?
获取 GET 请求参数
-
默认方式获取参数
@RequestMapping(value = "/message", method = RequestMethod.GET) public String hello(String msg) { return msg; }
默认情况下会从URL后面的拼接参数中获取参数名是 msg 的值。
例如:http://localhost:8080/api/message?msg=hello -
使用@RequestParam
@RequestMapping(value = "/message", method = RequestMethod.GET) public String hello(@RequestParam(value = "msg",required = false) String msg) { return msg; }
-
使用HttpServletRequest
@RequestMapping(value = "/message", method = RequestMethod.GET) public String hello(HttpServletRequest request) { return request.getParameter("msg"); }
这个方法是获取整个URL的信息,然后手动获取参数。这个里面带的内容很多,不仅请求参数还有Header,Cookies等信息。
获取 POST 请求参数
POST 传递参数可以大致分成两种,一种是表单:在 servlet 实现中mutipart/form-data 和 application/x-www-form-urlencoded 会被特殊处理,请求参数将被放置于request.paramter中解析成map。第二种是 application/json(请求参数放在body体中),参数是存放在json中的,参数必须要用@RequestBody才能解析出来。
-
使用@RequestBody
@RequestMapping(value = "/message", method = RequestMethod.POST) public String hello(@RequestBody String msg) { return msg; }
这种传递方式必须使用Content-Type=application/json,这个不仅可以指定msg为具体对象,也可以用Map、JSONObject、实体对象等。
-
使用@RequestParam
@RequestMapping(value = "/message", method = RequestMethod.POST) public String hello(@RequestParam String msg) { return msg; }
这种方式只用在Content-Type=mutipart/form-data和Content-Type=application/x-www-form-urlencoded这种情况下才能使用,servlet将Body体中的key-value转成参数对。
如果是这种方式,URL后面拼接参数对,也就是类似Get请求的方式,这样的post请求,@RequestParam是能够获取后面的参数。
有一个有趣的现象,如果Content-Type=mutipart/form-data,Body中加入参数和URL后面拼接参数一起做Post请求,都可以被加载到参数对中,如果是同名的,只取用form-data(Body体)中的。
如果Content-Type=application/x-www-form-urlencoded和URL拼接的一起,如果是String类型,则两个值会被拼接,其他类型取的是URL拼接的参数。 -
使用 HttpServletRequest
和Get方式一样,这是个通用的方式。这个也可以和URL拼接的一起搭,但是没有RequestParam的String类型值被拼接问题,优先级 form-data高于URL拼接高于x-www-form-urlencoded。
接下来使用httpClient 模拟请求 POST 方式(GET 请求就不模拟了)
-
拼接url的方式
public class MainHttpPost { public static void main(String[] args) throws ClientProtocolException, IOException { CloseableHttpClient httpClient = HttpClientBuilder.create().build(); Map<String, String> params = new HashMap<>(); params.put("msg", "hellp"); String postUrl = postUrlEncodedWithParams("http://localhost:9999/api/message", params); HttpPost httpPost = new HttpPost(postUrl); httpPost.setHeader("Content-Type", "application/json;charset=UTF-8"); CloseableHttpResponse response = httpClient.execute(httpPost); String content = EntityUtils.toString(response.getEntity()); System.out.println(content); } public static String postUrlEncodedWithParams(String url, Map<String, String> params) throws UnsupportedEncodingException { StringBuilder param = new StringBuilder(url + "?"); // 将要拼接的参数urlencode for (Entry<String, String> entry : params.entrySet()) { param.append(entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), "UTF-8") + "&"); } return param.toString(); } }
-
使用表单方式提交
public class MainHttpPost { public static void main(String[] args) throws ClientProtocolException, IOException { CloseableHttpClient httpClient = HttpClientBuilder.create().build(); HttpPost httpPost = new HttpPost("http://localhost:9999/api/message"); httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded"); List<NameValuePair> list = new ArrayList<>(); BasicNameValuePair basicNameValuePair = new BasicNameValuePair("msg", "hello"); list.add(basicNameValuePair); UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list); httpPost.setEntity(entity); CloseableHttpResponse response = httpClient.execute(httpPost); String content = EntityUtils.toString(response.getEntity()); System.out.println(content); } }
-
错误的请求方式:
使用 @RequestParam
@RequestMapping(value = "/message", method = RequestMethod.POST) public String hello(@RequestParam String msg) { return msg; }
public class MainHttpPost { public static void main(String[] args) throws ClientProtocolException, IOException { CloseableHttpClient httpClient = HttpClientBuilder.create().build(); Map<String, String> params = new HashMap<>(); params.put("msg", "hellp"); StringEntity entity = new StringEntity(JSON.toJSONString(params),"UTF-8"); HttpPost httpPost = new HttpPost("http://localhost:9999/api/message"); httpPost.setEntity(entity); httpPost.setHeader("Content-Type", "application/json;charset=UTF-8"); CloseableHttpResponse response = httpClient.execute(httpPost); String content = EntityUtils.toString(response.getEntity()); System.out.println(content); } }
这样请求会报错:
org.springframework.web.bind.MissingServletRequestParameterException: Required xxx parameter ‘msg’ is not present源码的分析过程可以参考这篇文章,也可以自己打断点追踪源码:
由MissingServletRequestParameterException产生原因的分析