接口偶发报错缺少必填参数,经过排查分析,发现报错缺少参数时,其实客户端是传了参数的。
经过分析发现,这种现象是把HttpServletRequest对象传到了异步线程中使用引起的。
Request对象是tomcat创建的,用于封装一次http请求相关的信息,当一次请求结束后,Request对象并不会被销毁,Request对象会被回收,tomcat只是把Request对象中的所有属性重置为初始状态,清空参数,然后放入队列,供其他请求使用。
tomcat源码分析
tomcat 包含三种 EndPoint:
org.apache.tomcat.util.net.NioEndpoint.java
org.apache.tomcat.util.net.JIoEndpoint.java
org.apache.tomcat.util.net.AprEndpoint.java
EndPoint 用于接受客户端 socket 请求(由内部类 Poller 完成),然后对 Socket 进行简单处理(由内部类 SocketProcessor 完成),接着转交给对应的 ProtocolProcessor 进行处理,封装成 request,通过各个Container 后 到达 servlet。
Processor回收栈
每次处理客户端请求,会先从Processor回收栈(recycleProcessors)中pop出一个processor对象,如果没取到,则会创建一个Processor。(Processor回收栈的默认大小是200,可通过max_connection参数调整)
首次创建processor
每创建一个Processor对象,会同时创建一个Request、Response对象,Processor对象和Request、Response是一一对应的。(所以Request对象是跟随Processor对象进行回收并循环复用的)
请求处理结束,释放processor资源
回收资源
request.getParameter()方法,会判断是否解析过参数,第一次调用该方法,会解析参数(byte转成String),然后放到paramHashValues中,后面通过getParameter获取参数会去paramHashValues中取。
当一次请求结束后,本次请求的Processor对象中的所有元素属性(包括request、response等)会重置,然后Processor对象会push进入processor回收栈,供下次请求使用。
如果把Request对象传入了异步线程,异步线程执行比较慢时,会出现Processor对象已经放入回收栈中,下一次请求拿到了这个Processor对象,并使用其中的Request对象,在通过Request对象取参数前,异步线程中通过Request.getParameter获取参数,此时,会把didQueryParameters置为true,
而此时新的请求中还没有执行到给Request对象赋值,此时就出现获取不到参数的现象。
本地模拟异步传递Request对象的场景进行debug验证
把Request对象传入异步线程,异步线程sleep10秒(确保同步请求处理结束),然后通过Request对象获取参数。
预期结果:等待10秒待异步线程获取Request中的参数后,再通过postman请求该接口,此时会报缺少参数异常。
由于本地调试每次只发1个请求,所以第一次访问接口新建Processor对象以及Request对象,后续请求都是使用的第一次请求创建的Processor。
出现缺少参数异常时,getParameter查询到的数据