Servlet的Request.getInputStream()只能读取一次问题
Servlet的Request.getInputStream()只能读取一次问题
这个星期公司的项目接口进行改造,公司的接口有的采用了WebService的方式,有的使用的是Http协议+Servlet的形式,对于WebService的形式还真没有接触过,闲着没事的时候学习一下,毕竟新接口都采用这种方式,也是一种趋势。在改造Http协议+Servlet的接口过程中对Http协议和Servlet又有了一个新的认识,特别是Http协议,以前脑子里乱乱的,知道有这个东西,知道它是做什么的,很模糊,做web开发的还是需要深刻理解Http协议的。在接口改造的过程中需要Servlet读取客户端提交过来的XML,所以用到了Request.getInputStream API,碰到的问题是以前的旧接口中写了两次Request.getInputStream(),最后一次读取的内容为空,虽然为空,但是没有影响接口的使用,所以一直放在这里没有人管,今天发现了研究了一下原因,也能把没用的代码删除掉。
为什么第二次在Servlet中获取InputStream的值为空,读取不到XML内容,这个问题要复习一下java中IO的知识了,在java中读取一个文件或者字符串的内容的代码大家都会写,下边是使用ByteArrayInputStream和ByteArrayOutputStream进行演示:

1 @Test
2 public void testByteArrayInputStream() throws Exception {
3
4 String str = "AAAAACCCCcCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
5 //ByteArrayInputStream是把一个byte数组转换成一个字节流,读取的内容是从byte数组中读取的
6 ByteArrayInputStream byteInputStream = new ByteArrayInputStream(str.getBytes());
7
8 //ByteArrayOutputStream生成对象的时候,是生成一个100大小的byte的缓冲区,写入的时候,是把内容写入内存中的一个缓冲区
9 ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(100);
10
11 int i =0;
12 byte [] b = new byte[100];
13 while((i = byteInputStream.read(b))!= -1){
14 byteOutput.write(b, 0, i);
15 }
16 System.out.println(new String(byteOutput.toByteArray()));
17
18 }

打印结果是:AAAAACCCCcCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
把一个String字符串的内容使用ByteArrayOutputStream读取出来,然后打印显示。这个代码没有什么问题,估计大家都能写出来,但是看一下下边添加一行代码之后的内容:

1 @Test
2 public void testByteArrayInputStream() throws Exception {
3 String str = "AAAAACCCCcCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
4 //ByteArrayInputStream是把一个byte数组转换成一个字节流,读取的内容是从byte数组中读取的
5 ByteArrayInputStream byteInputStream = new ByteArrayInputStream(str.getBytes());
6
7 //调用这个方法,会影响到下次读取,下次再调用这个方法,读取的起始点会后移5个byte
8 byteInputStream.read(new byte[5]);
9
10 //ByteArrayOutputStream生成对象的时候,是生成一个100大小的byte的缓冲区,写入的时候,是把内容写入内存中的一个缓冲区
11 ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(100);
12
13 int i =0;
14 byte [] b = new byte[100];
15 while((i = byteInputStream.read(b))!= -1){
16 byteOutput.write(b, 0, i);
17 }
18 System.out.println(new String(byteOutput.toByteArray()));
19 }

打印结果是:CCCCcCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
我在第8行添加了一行代码,这行代码可以把String生成的byte数组中读取5个字节的内容,这行代码会影响到后边第15行byteInputStream的读取结果,显然”AAAAA“在输出中没有了,这是为什么呢?我感觉需要查看java中ByteArrayInputStream的Read方法的实现源码,查看的结果其实可以总结一个句话,就是在InputStream读取的时候,会有一个pos指针,他指示每次读取之后下一次要读取的起始位置,在API文档中是这样解释的:(看过InputStream源码的都明白,read方法其实调用的都是带有三个参数的方法)

1 public int read(byte[] b,int off, int len)
2 Reads up to len bytes of data into an array of bytes from this input stream. If pos equals count, then -1 is returned to indicate end of file. Otherwise, the number k of bytes read is equal to the smaller of len and count-pos. If k is positive, then bytes buf[pos] through buf[pos+k-1] are copied into b[off] through b[off+k-1] in the manner performed by System.arraycopy. The value k is added into pos and k is returned.

就是在每次读取的时候会更新pos的值,当你下次再来读取的时候是从pos的位置开始的,而不是从头开始,所以第二次获取String中的值的时候是不全的,”AAAAA“丢掉了,这也就导致了两次调用request.getInputStream,第二次的时候肯定获取不了值,因为第一次读取完成之后pos指针在末尾,下次再读取肯定读取不到,同request.getInputStream两次调用返回的对象是同一个对象。读取的是同一个Stream。
但是仔细查看API文档你会发现有这样一个方法:
public void reset()
Resets the buffer to the marked position. The marked position is 0 unless another position was marked or an offset was specified in the constructor.
就是可以把pos的指针的位置重置为起始位置,但是调用它是有条件的,不是所有的IO读取流都能调用这个方法.看一下有这个方法
public boolean markSupported()
Tests if this input stream supports the mark and reset methods. Whether or not mark and reset are supported is an invariant property of a particular input stream instance. The markSupported method of InputStream returns false.
这个方法可以判断是不是支持reset()方法的调用,我也没有试servlet的InputStream是否可以调用,直接查看了一下Servlet的源码。
request.getInputStream返回的其实ServletInputStream,查看一下源码你会发现:ServletInputStream继承了InputStream同时没有重写reset()方法,查看一下InputStream源码:
InputStream的reset()方法源码是这样的:
1 public synchronized void reset() throws IOException {
2 throw new IOException("mark/reset not supported");
3 }
调用reset方法直接抛出异常,所以ServletInputStream是不能调用reset方法,这就导致了只能调用一次getInputStream(),第二次调用的时候没有办法获取到InputStream流中的原因。现在我们更改一下上边读取String字符串的例子:

1 @Test
2 public void testByteArrayInputStream() throws Exception {
3 String str = "AAAAACCCCcCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
4 //ByteArrayInputStream是把一个byte数组转换成一个字节流,读取的内容是从byte数组中读取的
5 ByteArrayInputStream byteInputStream = new ByteArrayInputStream(str.getBytes());
6
7 //调用这个方法,会影响到下次读取,下次再调用这个方法,读取的起始点会后移5个byte
8 byteInputStream.read(new byte[5]);
9 byteInputStream.reset();//调用reset方法可以使read中的pos指针复位
10
11
12 //ByteArrayOutputStream生成对象的时候,是生成一个100大小的byte的缓冲区,写入的时候,是把内容写入内存中的一个缓冲区
13 ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(100);
14
15 int i =0;
16 byte [] b = new byte[100];
17 while((i = byteInputStream.read(b))!= -1){
18 byteOutput.write(b, 0, i);
19 }
20 System.out.println(new String(byteOutput.toByteArray()));
21 }

打印结果是:AAAAACCCCcCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
在第8行添加一行代码,byteInputStream调用reset()方法,打印的结果回复了正常,我们看一下ByteInputStream中reset()的源码:

1 /**
2 * Resets the buffer to the marked position. The marked position
3 * is 0 unless another position was marked or an offset was specified
4 * in the constructor.
5 */
6 public synchronized void reset() {
7 pos = mark;
8 }

这一次没有抛出异常,而是把mark的值赋值给pos了,mark的值为0,所以在调用reset()方法之后可以从头开始读取。
进过查看JDK的源码分析为什么Servlet中读取InputSteam只能读取一次的问题,果断把没用的代码删除了,只有明白原理明白为什么,在写代码的时候才能很肯定的明白自己写的代码一定是正确的或者是错误的,不用再去运行一下试试。
以后闲着没事多看JDK的源码,看源码的确是一个很好的学习的过程,到此结束。
Servlet的Request.getInputStream()只能读取一次问题的更多相关文章
- Request.getInputStrema只能读取一次的分析过程
1. 我们先来看一下继承关系HttpServletRequest 接口继承ServletRequest接口 public abstract interface ServletRequest{ pub ...
- 解决SpringMVC拦截器中Request数据只能读取一次的问题
解决SpringMVC拦截器中Request数据只能读取一次的问题 开发项目中,经常会直接在request中取数据,如Json数据,也经常用到@RequestBody注解,也可以直接通过request ...
- HttpServletRequest.getInputStream() 只能读取一次
问题:在使用HTTP协议实现应用间接口通信时,服务端读取客户端请求过来的数据,会用到request.getInputStream(),第一次读取的时候可以读取到数据,但是接下来的读取操作都读取不到数据 ...
- 解决 request.getInputStream() 只能获取一次body的问题
问题: 在使用HTTP协议实现应用间接口通信时,服务端读取客户端请求过来的数据,会用到request.getInputStream(),第一次读取的时候可以读取到数据,但是接下来的读取操作都读取不到数 ...
- springboot使用百度富文本UEditor遇到的问题一览(springboot controller中request.getInputStream无法读取)
先吐槽一下UEditor作为一个前端的js类库,非要把4种后端的代码给出来,而实际生产中用的框架不同,其代码并不具有适应性.(通常类似其它项目仅仅是给出数据交互的规范.格式,后端实现就可以自由定制) ...
- request.getInputStream() 流只能读取一次问题
问题: 一次开发过程中同事在 sptring interceptor 中获取 request body 中值,以对数据的校验和预处理等操作 .导致之后spring 在读取request body 值做 ...
- httpServletRequest中的流只能读取一次的原因
首先,我们复习一下InputStream read方法的基础知识, java InputStream read方法内部有一个,postion,标志当前流读取到的位置,每读取一次,位置就会移动一次,如果 ...
- 拦截器中,request中getReader()和getInputStream()只能调用一次,构建可重复读取inputStream的request.
由于 request中getReader()和getInputStream()只能调用一次 在项目中,可能会出现需要针对接口参数进行校验等问题. 因此,针对这问题,给出一下解决方案 实现方法:先将Re ...
- Request的Body只能读取一次解决方法
一.需要一个类继承HttpServletRequestWrapper,该类继承了ServletRequestWrapper并实现了HttpServletRequest, 因此它可作为request在F ...
随机推荐
- 安装ruby on rail
安装: # nvm 安装, 两种方法 $ curl https://raw.githubusercontent.com/creationix/nvm/v0.8.0/install.sh | sh $ ...
- <<Python基础课程>>学习笔记 | 文章13章 | 数据库支持
备注:本章介绍了比较简单,只是比较使用样品,主要假设是把握连接,利用数据库.和SQLite做演示样本 ------ Python数据库API 为了解决Python中各种数据库模块间的兼容问题,如今已经 ...
- CKPlayer从Cookie里读取上次播放记录的一个demo
<!DOCTYPE html><html><head> <meta http-equiv="Content-Type" content=& ...
- Android在真机调试的设置方法
1. 设置android手机为USB调试模式.步骤: menu---> 设置 ---> 应用程序 ---> 开发 , 选择[USB调试] 2. 用USB连接手机和电脑,并确保成功.步 ...
- Oracle 11g sys,system 密码忘记设置解决办法
原文:Oracle 11g sys,system 密码忘记设置解决办法 1.启动sqlplus 2.请输入用户名: sqlplus/as sysdba 3.输入口令: 直接回车 4.连接到: Ora ...
- Linux多命令顺序运行
- appserv+win8
在win8系统安装appserv后,假设apache2服务不安装成功,在安装文件夹Apache2.2手动执行apache_servicefix.bat要么apache_serviceinstall.b ...
- 对于发Github的contributions贡献不会增加
最近发现每天在 Github 做代码提交,可是 contributions 的面板(贡献图)上的绿点(即贡献值)却没有增长了.擦~ 有两个礼拜了. 例如以下图并且.同一时候发现曾经的绿点也是稀稀拉拉的 ...
- 开发环境准备:Ruby on Rails开发环境配置
开发环境准备:Ruby on Rails开发环境配置 前情回顾 上次讲到Vmware虚拟机的安装配置以及Scientific Linux 6.X系统的安装.这回我们的主要任务是在Linux操作系统上完 ...
- Android项目---语言适配
android多国语言文件夹 android多国语言文件夹文件汇总如下:(有些语言的书写顺序可能跟中文是相反的) 中文(中国):values-zh-rCN 中文(台湾):values-zh-rTW 中 ...