前言:

最近写http接口时,有了很多关于接口取参方式的疑问,大家都知道 Servlet常用的取参方式有 getParameter、getParameterValues、getInputStream(读流形式)。SpringMvc 常用的有封装好的 @RequestParam ,RequestBody 。这些取参方式都有什么特点,我都写了测试接口,利用postman 做了测试。通过测试现象得到了如下结论,如有错误,请指正。

测试结论 :

HttpServletRequest

1、getParameter() 取 Key- Value形式的值(URL带参+Form Data) 相同Key只取第一个值,且优先取 url上带参的值。

2、getParameterValues() 取Key - Value全部值, 取Key的所有值。

3、Get方式 以 Query String Paramters 形式传参(参数在URL上),报文体中无值 无法用 getInputStream() 取报文值。

4、Post方式 application/x-www-form-urlencoded 形式 getParameter() 和 getInputStream() 都可取到值,且 getParameter() 和 getInputStream() 互斥。

5、Post方式 application/json 形式 ,只能由 getInputStream()读取。

SpringMvc 

1、@RequestParam String param  : 读取 Key - Value 形式的值 ,与 getParameterValues() 一样取key的所有值

2、@RequestBody String param : Query String Paramters + application/x-www-form-urlencoded  的 Key -Value形式,全部读取 例如:

<-- // 接口请求报文
POST /test/testPost10?name=123&amp;password=456 HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: 20c00b49-28fb-5556-253b-1be56a6f251c

name=abc&password=efg

-->  // 接口取到的值
name=123&name=abc&password=456&password=efg

3、@RequestBody String param : Query String Paramters + application/json 只读取 报文体中的值 例如:

<--
POST /test/testPost10?name=123&amp;password=456&amp;token=wsx HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: c1d1bb38-04f5-2872-0488-89a7085e01be

{
    "name":"YHN",
    "password":"IJN"
    
}

--> 
{
    "name":"YHN",
    "password":"IJN"
    
}

4、@RequestBody Object obj 只读取 application/json 形式的报文

探本朔源,我们通过源码来验证上面得到测试结论。

getParameter 和 getParameterValues 源码解析

直接在web项目中debug 会找到org.apache.catalina.connector.RequestFacade.java 这个类,是在tomcat中实现的。直接在github上 下载 tomcat 源码(我下的是最新版 tomcat9)

RequestFacade 中有 getParameter 和 getParameterValues方法 ,其中调用了 org.apache.catalina.connector.Request.java 中的方法这两个方法。我们直接去看 org.apache.catalina.connector.Request.java 中的 getParameter(String name) 和getParameterValues(String name) 方法。截取代码如下:

   /*** @return the value of the specified request parameter, if any; otherwise,* return <code>null</code>.  If there is more than one value defined,* return only the first one.** @param name Name of the desired request parameter*/@Overridepublic String getParameter(String name) {if (!parametersParsed) {parseParameters();}return coyoteRequest.getParameters().getParameter(name);}/*** @return the defined values for the specified request parameter, if any;* otherwise, return <code>null</code>.** @param name Name of the desired request parameter*/@Overridepublic String[] getParameterValues(String name) {if (!parametersParsed) {parseParameters();}return coyoteRequest.getParameters().getParameterValues(name);}

其中有一个关键函数 parseParameters() 附上解析代码

   /*** Parse request parameters.*/protected void parseParameters() {parametersParsed = true;  // 设置解析标志 ,后面再使用getParamter()时不再次解析Parameters parameters = coyoteRequest.getParameters();boolean success = false;try {// Set this every time in case limit has been changed via JMXparameters.setLimit(getConnector().getMaxParameterCount());// getCharacterEncoding() may have been overridden to search for// hidden form field containing request encodingCharset charset = getCharset();boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();parameters.setCharset(charset);   // 设置字符集if (useBodyEncodingForURI) {parameters.setQueryStringCharset(charset);  }// Note: If !useBodyEncodingForURI, the query string encoding is//       that set towards the start of CoyoyeAdapter.service()// 1、解析 Query String Paramters形式(url带参形式) parameters.handleQueryParameters();  if (usingInputStream || usingReader) {success = true;return;}String contentType = getContentType();if (contentType == null) {contentType = "";}int semicolon = contentType.indexOf(';');if (semicolon >= 0) {contentType = contentType.substring(0, semicolon).trim();} else {contentType = contentType.trim();}if ("multipart/form-data".equals(contentType)) {parseParts(false);   // 解析 multipart/form-datasuccess = true;return;}if( !getConnector().isParseBodyMethod(getMethod()) ) {success = true;return;}if (!("application/x-www-form-urlencoded".equals(contentType))) {success = true;return;}// 2、根据上面的 contentType 判断 下面解析 application/x-www-form-urlencoded 形式      int len = getContentLength();  // 报文体的长度 get方式 无报文体if (len > 0) {int maxPostSize = connector.getMaxPostSize();if ((maxPostSize >= 0) && (len > maxPostSize)) {Context context = getContext();if (context != null && context.getLogger().isDebugEnabled()) {context.getLogger().debug(sm.getString("coyoteRequest.postTooLarge"));}checkSwallowInput();parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);return;}byte[] formData = null;if (len < CACHED_POST_LEN) {if (postData == null) {postData = new byte[CACHED_POST_LEN];}formData = postData;} else {formData = new byte[len];}try {if (readPostBody(formData, len) != len) {  // readPostBody方法(代码已截取)读取报文体的流  使用getStream().read() 方法 。注意:流只能读取一次。parameters.setParseFailedReason(FailReason.REQUEST_BODY_INCOMPLETE);return;}} catch (IOException e) {// Client disconnectContext context = getContext();if (context != null && context.getLogger().isDebugEnabled()) {context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"),e);}parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);return;}parameters.processParameters(formData, 0, len);  // 解析application/x-www-form-urlencoded} else if ("chunked".equalsIgnoreCase(coyoteRequest.getHeader("transfer-encoding"))) {byte[] formData = null;try {formData = readChunkedPostBody();} catch (IllegalStateException ise) {// chunkedPostTooLarge errorparameters.setParseFailedReason(FailReason.POST_TOO_LARGE);Context context = getContext();if (context != null && context.getLogger().isDebugEnabled()) {context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"),ise);}return;} catch (IOException e) {// Client disconnectparameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);Context context = getContext();if (context != null && context.getLogger().isDebugEnabled()) {context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"),e);}return;}if (formData != null) {parameters.processParameters(formData, 0, formData.length);}}success = true;} finally {if (!success) {parameters.setParseFailedReason(FailReason.UNKNOWN);}}}/*** Read post body in an array.** @param body The bytes array in which the body will be read* @param len The body length* @return the bytes count that has been read* @throws IOException if an IO exception occurred*/protected int readPostBody(byte[] body, int len)throws IOException {int offset = 0;do {int inputLen = getStream().read(body, offset, len - offset);if (inputLen <= 0) {return offset;}offset += inputLen;} while ((len - offset) > 0);return len;}

从上面的代码可知 :Parameters 中有两个方法去解析 paramter 的值
1、parameters.handleQueryParameters();
2、parameters.processParameters(formData, 0, len);

我们找到 org.apache.tomcat.util.http.Parameters.java 看这两个方法及相关方法:

/** Process the query string into parameters  解析 query string 即Url带参*/public void handleQueryParameters() {if (didQueryParameters) {return;}didQueryParameters = true;if (queryMB == null || queryMB.isNull()) {return;}if(log.isDebugEnabled()) {log.debug("Decoding query " + decodedQuery + " " + queryStringCharset.name());}try {decodedQuery.duplicate(queryMB);} catch (IOException e) {// Can't happen, as decodedQuery can't overflowe.printStackTrace();}processParameters(decodedQuery, queryStringCharset); // 解析} public void processParameters( byte bytes[], int start, int len ) {processParameters(bytes, start, len, charset);}private void processParameters(byte bytes[], int start, int len, Charset charset) {if(log.isDebugEnabled()) {log.debug(sm.getString("parameters.bytes",new String(bytes, start, len, DEFAULT_BODY_CHARSET)));}int decodeFailCount = 0;int pos = start;int end = start + len;while(pos < end) {int nameStart = pos;int nameEnd = -1;int valueStart = -1;int valueEnd = -1;boolean parsingName = true;boolean decodeName = false;boolean decodeValue = false;boolean parameterComplete = false;do {switch(bytes[pos]) {case '=':if (parsingName) {// Name finished. Value starts from next characternameEnd = pos;parsingName = false;valueStart = ++pos;} else {// Equals character in valuepos++;}break;case '&':if (parsingName) {// Name finished. No value.nameEnd = pos;} else {// Value finishedvalueEnd  = pos;}parameterComplete = true;pos++;break;case '%':case '+':// Decoding requiredif (parsingName) {decodeName = true;} else {decodeValue = true;}pos ++;break;default:pos ++;break;}} while (!parameterComplete && pos < end);if (pos == end) {if (nameEnd == -1) {nameEnd = pos;} else if (valueStart > -1 && valueEnd == -1){valueEnd = pos;}}....省略代码....  //看上面的 switch() 方法 和 addParameter(name, value) 方法try {String name;String value;if (decodeName) {urlDecode(tmpName);}tmpName.setCharset(charset);name = tmpName.toString();if (valueStart >= 0) {if (decodeValue) {urlDecode(tmpValue);}tmpValue.setCharset(charset);value = tmpValue.toString();} else {value = "";}try {addParameter(name, value);} catch (IllegalStateException ise) {....省略代码....}} catch (IOException e) {....省略代码....}}

现在已找到最核心的方法了,    processParameters() 通过switch() 方法解析 Key - Value 值 在调用 addParameter(name, value)方法,
下面来看 addParameter()方法:

    public void addParameter( String key, String value )throws IllegalStateException {if( key==null ) {return;}parameterCount ++;if (limit > -1 && parameterCount > limit) {// Processing this parameter will push us over the limit. ISE is// what Request.parseParts() uses for requests that are too bigsetParseFailedReason(FailReason.TOO_MANY_PARAMETERS);throw new IllegalStateException(sm.getString("parameters.maxCountFail", Integer.valueOf(limit)));}ArrayList<String> values = paramHashValues.get(key);  // 根据key 取出一个 List出来 (同一个key,用List保存多个value值 )if (values == null) {values = new ArrayList<>(1);paramHashValues.put(key, values);}values.add(value);  // 保存value值}

这一步让我们找到了 parameter 解析后在内存的保存的位置 paramHashValues

 private final Map<String,ArrayList<String>> paramHashValues = new LinkedHashMap<>();

final 一个 LinkedHashMap (进入的顺序与被取出的顺序一致)。到了这一步已经可以到如下结论:

HttpServletRequest 调用 getParameter方法 或 getParameterValues方法时,实际是通过 org.apache.catalina.connector.RequestFacade.java 来调用 org.apache.catalina.connector.Request.java中的 getParameter方法 或 getParameterValues方法,其中通过 parseParameters 解析报文 ,先解析Query String Paramters(url带参),再解析 Form -Data 形式的值。最终把解析的报文值存入 org.apache.tomcat.util.http.Paramters.java 中 Map<String,ArrayList<String>> 中。

现在回过头 来看org.apache.tomcat.util.http.Parameters.java 中的 getParameter() 和 getParameterValues 方法

   // 取 key的第一个Vaule值public String getParameter(String name ) {handleQueryParameters();ArrayList<String> values = paramHashValues.get(name);if (values != null) {if(values.size() == 0) {return "";}return values.get(0);} else {return null;}}// 取 key的全部Value值public String[] getParameterValues(String name) {handleQueryParameters();// no "facade"ArrayList<String> values = paramHashValues.get(name);if (values == null) {return null;}return values.toArray(new String[values.size()]);}

现在就可以理解了 为什么 getParameter 老是优先取url上带的参数了。且只取一个值了,getParameterValues 取Key的所有值。

现在 HttpServletRequest 的 结论 1 和 2 已验证完毕。

下一篇 来看看  getInputStream() 和 getReader()。

http接口取参方式 - getParameter 和 getParameterValues相关推荐

  1. 8、SpringBoot+Mybatis整合------参数取值方式

    前言: 我们知道,在mybatis中,参数取值方式有两种: #{ } 和 ${ } 下面,我们来探讨下#{ }与${ }不同. 一.#{ } 例: select * from student wher ...

  2. Struts2三种传参方式(从jsp页面传到Action)

    多的不说,直接上代码;struts.xml代码:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYP ...

  3. 接口入参形式_花椒测试平台 接口篇

    背景 先来说说花椒测试平台的由来: 目的1,降低接口测试对测试人员代码能力的要求.测试人员只需要知道接口的url,请求参数,以什么样的格式传个服务端,接口的响应数据里需要验证哪个字段的值即可进行测试, ...

  4. 【Postman】测试 Post 请求时传递数据失败 常见传参方式

    近期尝试使用 nod.js + vue 开发一个小项目. 第一次使用 postman 测试,遇到 POST 请求数据发送不过去的问题,做一个记录. JS 部分代码 //用户注册接口 router.po ...

  5. 接口入参形式_某小公司RESTful、共用接口、前后端分离、接口约定的实践

    点击上方"后端技术精选",选择"置顶公众号" 技术文章第一时间送达! 作者:邵磊 juejin.im/post/59eafab36fb9a045076eccc3 ...

  6. 接口入参形式_极光小课堂|手把手教你做接口测试

    接口测试是项目测试过程中非常重要的一环,测试的对象是接口,所以提早介入测试,对代码逻辑进行全面验证,就会更早的发现程序的问题.同时,接口测试比UI测试效率更高,并且更容易验证极端和异常的情况. 那么什 ...

  7. mybatis传入数组怎么写_推荐的MyBatis传参方式List、数组等

    MyBatis 推荐的传参方式 如果要详细的学习 MyBatis,推荐看这个教程:https://www.w3cschool.cn/mybatis/ 1. 单个参数//接口方法int getAgeBy ...

  8. springboot接口入参下划线转驼峰以及返回参数驼峰转下划线实现

    转自:springboot接口入参下划线转驼峰以及返回参数驼峰转下划线实现 - 李东平|一线码农 - 博客园 (cnblogs.com) 1.背景 在实际开发中,通常来说java里面是使用驼峰的命名规 ...

  9. axios链接带参数_axios常见传参方式

    1:get请求 一般发送请求是这么写 axios.get('/user?id=12345&name=user') .then(function (res) { console.log(res) ...

  10. Jsp页面的几种传参方式

    Jsp页面的几种传参方式 1.JavaScript传参:这种传参方式用opener关键字, 可以实现跨页传参.其用法就是用opener关键字调用父窗体的一个组件. 举例:    opener.myfo ...

最新文章

  1. [2DPIC调试笔记]parameter_antenna_radiation1013(3)
  2. lnmp的编译安装mysql_LNMP环境搭建(二) 编译安装MySQL
  3. Mysql不同存储引擎的表转换方法
  4. 定制CentOS 5.6精简的自动安装版及心得
  5. ARouter 源码历险记 (二)
  6. 自动驾驶 2-3 软件架构 Software Architecture
  7. 脚本之家python专题_一个简单的python读写文件脚本
  8. js读取excel文件
  9. java语言简介总结
  10. 远程服务器网刻系统,PXE网克工具免费的全自动PXE网刻工具
  11. linux esp分区,win10esp分区多大合适
  12. Java 删除session实现退出登录
  13. 目标检测-2019年4篇目标检测算法最佳综述
  14. 【web前端期末大作业】简单的学生网页作业源码 基于html css javascript jquery技术设计的音乐网站(44页)
  15. java支付逻辑_java 订单支付status逻辑
  16. 学习笔记(02):【中国PHP教育大牛高洛峰】亲授php教程-PHP整型(integer)和浮点型(float或double)...
  17. ac3168无线网卡驱动下载_英特尔面向Windows 10推出无线网卡驱动程序和图形命令中心应用更新...
  18. mysql语句distinct_MySQL DISTINCT语句
  19. 《Head First Java》学习整理
  20. python scipy.optimize.minimize多变量多参数优化

热门文章

  1. word:清除格式(设置快捷键)
  2. Windows下卸载pip的方法
  3. 项目范围管理__范围管理计划 与 范围说明书
  4. 嵌入式软件开发必备知识体系
  5. 200Smart PLC 与丹佛斯变频器MODBUS RTU通讯实例
  6. JavaScript基础知识总结(必看篇)
  7. matlab——取整函数
  8. realtek 8111E 网卡 修改MAC 地址
  9. 设计模式之责任链模式(Java实现)
  10. 免费文字识别软件有哪些?分享三款不错的软件