关于springmvc时request的getReader()和getInputStream()只能调用一次的解决办法

  最近准备在原有的SSM项目的基础上添加完善的日志分析,由于是APP的后台系统,之前在规划APP的时候,并没有在APP上做埋点的处理,而如果想要进行埋点处理的话,对于未能新升级的APP用户来说,就是去了意义,因为只要用户不升级,埋点就不能在他的APP中运行。所以,就考虑到了在后台的入口增加日志的监控。

  想法总是简单,但是在实际实现的过程中却还是遇到了问题。由于APP基本都采用公参的加密校验,然后采用POST请求传递JSON数据。对于一般的请求分析,比如每个时间段的访问量,或者每个方法每个某块的统计都简单,只要在拦截器中新增一个将数据扔到消息队列中,然后在消费端在进行日志的分析和处理即可。然后如果要针对每个用户在什么时间段,做了什么处理,问题就来了,因为这个时候就必须拿到post中的json参数。

  有些同学就说,这不是很简单么,直接request.getParameter()不就可以了吗?NO,post的json数据是通过流的方式传递的,并不可以直接读取。OK,那我们用request.getReader()拿到流然后转成字符串不就可以了么?那么问题来了,流是只能流一遍的,一旦读过了就不会再有了,具体的方法中就拿不到了。说到这里,其实访问根本就不会再进到方法体了,因为springmvc的DispatcherServlet就会抛出异常  getReader() has already been called for。。。。。。

  说了这么多,下面是重点啦,其实很简单,就是在过滤器处理request(如果没有过滤器,那就新增一个即可)

  实现方法:先将RequestBody保存为一个byte数组,然后通过Servlet自带的HttpServletRequestWrapper类覆盖getReader()和getInputStream()方法,使流从保存的byte数组读取。然后再Filter中将ServletRequest替换为ServletRequestWrapper。代码如下:

BodyReaderHttpServletRequestWrapper类包装ServletRequest,将流保存为byte[],然后将getReader()和getInputStream()方法的流的读取指向byte[]

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class AuthFilter implements Filter{

    public void destroy() {

    }

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if(request instanceof HttpServletRequest) {
            requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request);
        }
        if(null == requestWrapper) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(requestWrapper, response);
        }
    }

    /**
     * 初始化函数时,需要获取排除在外的url
     */
    public void init(FilterConfig config) throws ServletException {

    }
}

引用的类

 1 import java.io.BufferedReader;
 2 import java.io.ByteArrayInputStream;
 3 import java.io.IOException;
 4 import java.io.InputStreamReader;
 5
 6 import javax.servlet.ServletInputStream;
 7 import javax.servlet.http.HttpServletRequest;
 8 import javax.servlet.http.HttpServletRequestWrapper;
 9
10 import jodd.JoddDefault;
11 import jodd.io.StreamUtil;
12
13 public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
14
15     private final byte[] body;
16
17     public BodyReaderHttpServletRequestWrapper(HttpServletRequest request)throws IOException {
18         super(request);
19         body = StreamUtil.readBytes(request.getReader(), JoddDefault.encoding);
20     }
21
22     @Override
23     public BufferedReader getReader() throws IOException {
24         return new BufferedReader(new InputStreamReader(getInputStream()));
25     }
26
27     @Override
28     public ServletInputStream getInputStream() throws IOException {
29         final ByteArrayInputStream bais = new ByteArrayInputStream(body);
30         return new ServletInputStream() {
31
32             @Override
33             public int read() throws IOException {
34                 return bais.read();
35             }
36         };
37     }
38 }
时间: 2024-11-05 20:29:09

关于springmvc时request的getReader()和getInputStream()只能调用一次的解决办法的相关文章

request使用getReader()和getInputStream()获取请求参数报400错误

参考:http://liwx2000.iteye.com/blog/1542431 和http://stackoverflow.com/questions/7318632/java-lang-illegalstateexception-getreader-has-already-been-called-for-this-re 原因:ServletRequest中getReader()和getInputStream()只能调用一次的解决办法 最近使用spring mvc做项目,数据格式是json,

[经使用有效]Sqlserver2005附加数据库时出错提示操作系统错误5(拒绝访问)错误5120的解决办法

sqlserver2005附加数据库时出错提示操作系统错误5(拒绝访问)错误5120的解决办法 最近几天从网上找了几个asp.net的登录案例想要研究研究代码,结果在用 Sql Server2005附加数据库文件时弹出错误信息:如下图: ,一时无解,遂求助于百度谷歌,经过各种试验,特将解决办法整理于此,希望能帮到大家,同时如果有好的意见大家多多交流啊! 方案一:切换登录方式 出现这种情况是由于用“混合验证方式”(SQL Server身份验证)登录数据库造成的,只要将登录方式改为“windows身

vs2012打开低版本项目时 出现vs2012警告未能加载包“visual c++ package 解决办法

vs2012 打开 vs2010 项目时 提示的 错误信息. 解决办法 是下载一个 vs2012的 一个补丁包 http://www.microsoft.com/en-us/download/details.aspx?id=36020 初次安装成功后,调试 无法启用,关闭,重新打开项目 即可解决! ======ok. [在此谢谢网上提供解决方案的朋友们,谢谢你们!] vs2012打开低版本项目时 出现vs2012警告未能加载包"visual c++ package 解决办法

SQLServer2005+附加数据库时出错提示操作系统错误5(拒绝访问)错误5120的解决办法

SQLServer2005+ 附加数据库时出错提示操作系统错误5(拒绝访问)错误5120的解决办法 我们在用Sql SQLServer2005+附加数据库文件时弹出错误信息如下图的处理办法: 方案一:切换登录方式 出现这种情况是由于用"混合验证方式"(SQL Server身份验证)登录数据库造成的,只要将登录方式改为"windows身份验证方式"登录即可解决该问题,附加成功后再换用"混合验证模式"登陆就没问题了. 方案二:修改服务 选择 所有程序

excel 如何 筛选,以及筛选后,在复制时 显示 不可对多重选定区域使用此命令的解决办法

excel 如何 筛选,以及筛选后,在复制时 显示 不可对多重选定区域使用此命令的解决办法选中 第一行, 开始 筛选 选择 某一列 右下角的小三角 ,进行内容筛选 即可. excel 筛选后,在复制时 显示 不可对多重选定区域使用此命令解决办法筛选结束后,选择几列进行复制会提示"不可对多重选定区域使用此命令".那么就别单独选择几列了,而是全选.点击整个表格的左上角,这样筛选 后的结果就会都被选中.右击 "复制",然后 粘贴 到 sheet2工作表中.这样就把筛选的结

VUE.JS 使用axios数据请求时数据绑定时 报错 TypeError: Cannot set property 'xxxx' of undefined 的解决办法

正常情况下在data里面都有做了定义 在函数里面进行赋值 这时候你运行时会发现,数据可以请求到,但是会报错 TypeError: Cannot set property 'listgroup' of undefined 主要原因是: 在 then的内部不能使用Vue的实例化的this, 因为在内部 this 没有被绑定.可以看下 Stackoverflow 的解释: 解决办法: 1.用ES6箭头函数,箭头方法可以和父方法共享变量 2.在请求axios外面定义一下 var that=this 问题

VUE - 使用axios数据请求时数据绑定时 报错 TypeError: Cannot set property 'xxxx' of undefined 的解决办法

created() { var that=this axios.get('http://jsonplaceholder.typicode.com/todos') .then(function (res) { // handle success // console.log(res); that.todos = res.data }) .catch(function (error) { // handle error console.log(error); }) .finally(function

vs调试时底部输出调试信息“无法查找或打开 PDB 文件”解决办法

用VS调试程序时,有时会在VS底部的“输出”框中提示“无法查找或打开 PDB 文件”.这该怎么解决呢? 下面,我们以VS2013为例,来教大家解决办法. 工具/原料 VS 方法/步骤 打开VS2013,点击菜单“工具”-“选项”.   在选项窗口中,展开“调试”-“常规”,然后在右边的窗格中勾选“启用源服务器支持”.   然后展开“调试”-“符号”,勾选“Windows符号服务器”.   这时,会弹出一个警告对话框,无视点击“确定”即可.   最后,点击“确定”关闭选项窗口.   下面,我们再来

Eclipse部署Maven web项目到tomcat服务器时,没有将lib下的jar复制过去的解决办法

我们在做web开发是,经常都要在eclipse中搭建web服务器,并将开发中的web项目部署到web服务器进行调试,在此,我选择的是tomcat服务器.之前部署web项目到tomcat进行启动调试都很正常,今天突然出现无法启动情况,启动过程报如下错误: java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener at org.apache.catalina.loader.Webap