最近开发过程中,遇到一个问题,在向Tomcat服务器进行请求时候,发现,偶尔出现异常,但是在前端传递param参数,并且这个参数是required=true,但是出现了MissingServletRequestParameterException报错问题。
由于这个问题并不是经常出现,偶尔出现问题,使用Jmeter 作为模拟器,进行请求模拟,结果在模拟过程中有出现了同样MissingServletRequestParameterException报错信息。
传输参数时候,如果某个参数值并没有写 判断时候应该要 先判断 str==null,在去判断 str.isEmpty()
直接在这个异常类的函数设置断点,查看其为什么抛出这样的异常:
第一步:查看该异常父类结构图: 从结构中看到其属于ServletRequestBlindingException( 说其是属于Request信息中绑定)
但是在 前端传递参数过程,param是required,说明前端肯定传递参数,说明此参数值在传递过程中是不是被Spring框架给吃了?
直接找到handleMissingValue()方法,发现其中并没有解释,直接断点定位到 RequestParamMethodArgumentResolver的父类 AbstractNamedValueMethodArgumentResolver,
在其父类中我们发现如下代码: Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest); //在RequestParamMethodArgumentResolver中,直接通过调用request.getParameterValues(name)来获取普通请求的参数
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest); //如果没有获取到且没有默认值,就会在这里抛出异常。
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
从父类中分析 :request.getParameterValue() 里面并没有得到想要值,并且参数是 必须且没有默认的参数值 ,才会抛出对应异常,因此产生疑问:参数传递过程中难道被框架给吞了????
在此博客中写道: 用户的一个Request请求:将最原始的Socket进行一系列的包装,最后到达HttpServletRequest和HttpServletResponse
最原始的数据保存在org.apache.coyote.Request,Tomcat服务器 中HTTP1Process 类通过service方法传递给coyote,定位到Request请求中参数param出现问题;直接跳到:RequestmappingHandlerAdapter 这个类中 at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
设置断点:对于正常请求:didQueryParamters=false 但是在异常请求中 didQueryParamters=true;
这个参数判断query String 是否已经被解析过,并且在结束时候,会调用paramter的recycle方法,同时查看 其中对应的recyle()方法:当数据被解析之后,发现recycle()方法会将paramter参数进行清空,并且didQueryParameters 参数置为true. public void recycle() {
parameterCount = 0;
paramHashValues.clear(); //清空了解析后的parameter map
didQueryParameters=false; //是否被解析过,置成false
encoding=null;
decodedQuery.recycle();
parseFailedReason = null;
}
为什么paramters参数值在首次Request中会被解析: Tomcat 中,Request的对象会被循环使用,因此一定是在进入Spring 框架之前被解析过(或者是被异步执行过),因此在一次获取时候发现其出现 空值异常; 所以根本原因是,在Parameter被重置了之后, didQueryParameters
又被置成了true,导致新的请求参数没有被正确解析,就报错了(此时的parameterMap已经被重置,为空)。而 didQueryParameters
只有在一种情况下才会被置为true,也就是 handleQueryParameters
方法被调用时。而 handleQueryParameters
会在多个场景中被调用,其中一个就是 getParameterValues
,获取请求参数的值
最终通过全局搜索:引用HttpServeletRequest 地方,最终发现buryPoint()方法,这个方法属于异步执行方法,在请求结束之后依然调用Request.getParamter()方法,导致下一次请求解析参数不被解析:
@Async
public void buryPoint(long userId, HttpServletRequest request.....) {
if (request != null) {
xxx = request.getParameter("xxx");
}