当我们在编写java web服务端代码时,经常使用HttpServletRequest.getParameter(value)来获取http的参数,但你有没有发现只有getParameter而没有setParameter方法?
例如:https://www.pornhub.com/v?mode=mila&type=teen
假如pornhub服务端的逻辑是你提供,你只需对应的servlet方法中写上一句request.getParameter("mode")就能获取url传递的参数,但你并没有看到request.setParameter方法。
那么这个参数设置是谁做的呢? 答案是web服务器,tomcat,jetty等。
问题又来了,tomcat是如何实现参数设置的呢?
HttpServletRequest是一个接口,它定义了http请求对象中一系列操作方法,诸如:getHeader,getSession等。
getParameter方法在它的父级接口ServletRequest中,通过ideal跟踪其实现方法,分别经过ServletRequest->ServletRequestWrapper->httpServletRequestWrapper->ApplicationHttpRequest
最后具体实现定位于:tomcat-embed-core-9.0.37.jar org.apache.catalina.ApplicatonHttpRequest类中。
该类中定义了一个Map来存放http request的参数。
……
/**
* The request parameters for this request. This is initialized from the
* wrapped request.
*/
protected Map<String, String[]> parameters = null;
……
/**
* Override the <code>getParameter()</code> method of the wrapped request.
*
* @param name Name of the requested parameter
*/
@Override
public String getParameter(String name) {
parseParameters();
String[] value = parameters.get(name);
if (value == null) {
return null;
}
return value[0];
}
继续跟踪代码parseParameters方法,参数Map parameters在该方法中初始化,然后调用了mergeParameters()
private void mergeParameters() {
if ((queryParamString == null) || (queryParamString.length() < 1))
return;
// Parse the query string from the dispatch target
Parameters paramParser = new Parameters();
MessageBytes queryMB = MessageBytes.newInstance();
queryMB.setString(queryParamString);
……
paramParser.setQuery(queryMB);
paramParser.setQueryStringCharset(charset);
//处理queryParamString
paramParser.handleQueryParameters();
Enumeration<String> dispParamNames = paramParser.getParameterNames();
while (dispParamNames.hasMoreElements()) {
String dispParamName = dispParamNames.nextElement();
String[] dispParamValues = paramParser.getParameterValues(dispParamName);
String[] originalValues = parameters.get(dispParamName);
if (originalValues == null) {
parameters.put(dispParamName, dispParamValues);
continue;
}
//添加参数,数据来源于paramParser.getParameterNames()
parameters.put(dispParamName, mergeValues(dispParamValues, originalValues));
}
}
queryParamString即是http url的参数内容。paramParser.handleQueryParametes()方法对queryParamString加工。跟踪handleQueryParametes方法,我们看到该方法中有一段代码:
do {
switch(bytes[pos]) {
case '=':
if (parsingName) {
// Name finished. Value starts from next character
nameEnd = pos;
parsingName = false;
valueStart = ++pos;
} else {
// Equals character in value
pos++;
}
break;
case '&':
if (parsingName) {
// Name finished. No value.
nameEnd = pos;
} else {
// Value finished
valueEnd = pos;
}
parameterComplete = true;
pos++;
break;
case '%':
case '+':
// Decoding required
if (parsingName) {
decodeName = true;
} else {
decodeValue = true;
}
pos ++;
break;
default:
pos ++;
break;
}
} while (!parameterComplete && pos < end);
&,=是url传参时会使用的符号,%,+是url 编码时生成的符号,虽然整个方法处理很复杂,但整个逻辑其实很清楚,就是在处理queryParamString这个字符串。
此时我们可以得出结论:
1)HttpServletRequest的getParameter()方法是由顶级接口ServletRequest的现实类ApplicationHttpRequest的getParameter方法实现的。
2)ApplicationHttpRequest.getParameter具体实现方式是:将url传递的参数字符串通过paramParser类的handleQueryParametes方法进行处理,通过&=关键字符截取paramName和paramValue,最后存储在Map<String,String[]>的数据结构中。
3)为什么没有HttpServletRequest.setParameter方法?因为对应的实现类ApplicationHttpRequest的getParameter方法直接对queryParamString进行了截取处理。
接口ServletRequest -> 子接口:HttpServletRequest -> 实现类:HttpServletRequestWrapper -> 继承类:ApplicationHttpRequest