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()只能读取一次问题的更多相关文章

  1. Request.getInputStrema只能读取一次的分析过程

    1. 我们先来看一下继承关系HttpServletRequest 接口继承ServletRequest接口 public abstract interface  ServletRequest{ pub ...

  2. 解决SpringMVC拦截器中Request数据只能读取一次的问题

    解决SpringMVC拦截器中Request数据只能读取一次的问题 开发项目中,经常会直接在request中取数据,如Json数据,也经常用到@RequestBody注解,也可以直接通过request ...

  3. HttpServletRequest.getInputStream() 只能读取一次

    问题:在使用HTTP协议实现应用间接口通信时,服务端读取客户端请求过来的数据,会用到request.getInputStream(),第一次读取的时候可以读取到数据,但是接下来的读取操作都读取不到数据 ...

  4. 解决 request.getInputStream() 只能获取一次body的问题

    问题: 在使用HTTP协议实现应用间接口通信时,服务端读取客户端请求过来的数据,会用到request.getInputStream(),第一次读取的时候可以读取到数据,但是接下来的读取操作都读取不到数 ...

  5. springboot使用百度富文本UEditor遇到的问题一览(springboot controller中request.getInputStream无法读取)

    先吐槽一下UEditor作为一个前端的js类库,非要把4种后端的代码给出来,而实际生产中用的框架不同,其代码并不具有适应性.(通常类似其它项目仅仅是给出数据交互的规范.格式,后端实现就可以自由定制) ...

  6. request.getInputStream() 流只能读取一次问题

    问题: 一次开发过程中同事在 sptring interceptor 中获取 request body 中值,以对数据的校验和预处理等操作 .导致之后spring 在读取request body 值做 ...

  7. httpServletRequest中的流只能读取一次的原因

    首先,我们复习一下InputStream read方法的基础知识, java InputStream read方法内部有一个,postion,标志当前流读取到的位置,每读取一次,位置就会移动一次,如果 ...

  8. 拦截器中,request中getReader()和getInputStream()只能调用一次,构建可重复读取inputStream的request.

    由于 request中getReader()和getInputStream()只能调用一次 在项目中,可能会出现需要针对接口参数进行校验等问题. 因此,针对这问题,给出一下解决方案 实现方法:先将Re ...

  9. Request的Body只能读取一次解决方法

    一.需要一个类继承HttpServletRequestWrapper,该类继承了ServletRequestWrapper并实现了HttpServletRequest, 因此它可作为request在F ...

随机推荐

  1. 利用WebBrowser实现Web打印的分析

    原文:利用WebBrowser实现Web打印的分析 WebBrowser是IE内置的浏览器控件,无需用户下载.本文档所讨论的是有关IE6.0版本的WebBrowser控件技术内容.其他版本的IE应该也 ...

  2. Android Notification通知详细解释

    Android Notification通知具体解释  Notification: (一).简单介绍:         显示在手机状态栏的通知. Notification所代表的是一种具有全局效果的通 ...

  3. struts2文件下载 <result type="stream">

    <!--struts.xml配置--> <action name="download" class="com.unmi.action.DownloadA ...

  4. asp.net web api2.0 ajax跨域解决方案

    asp.net web api2.0 ajax跨域解决方案 Web Api的优缺点就不说了,直接说怎么跨域,我搜了一下,主要是有两种.  一,ASP.NET Web API支持JSONP,分两种 1, ...

  5. angular.js的路由和模板在asp.net mvc 中的使用

    angular.js的路由和模板在asp.net mvc 中的使用 我们知道angular.js是基于mvc 的一款优秀js框架,它也有一套自己的路由机制,和asp.net mvc 路由不太一样.as ...

  6. 对于GetBuffer() 与 ReleaseBuffer() 的一些分析

    先 转载一段别人的文章 CString类的这几个函数, 一直在用, 但总感觉理解的不够透彻, 不时还实用错的现象. 今天抽时间和Nico一起分析了一下, 算是拨开了云雾: GetBuffer和Rele ...

  7. A在SP.NET跨页多选

    在ASP.NET跨页多选 本文介绍怎样在ASP.NET中实现多页面选择的问题.其详细思路非常easy:用隐藏的INPUT记住每次选择的项目,在进行数据绑定时.检查保存的值,再在DataGrid中进行选 ...

  8. JQuery在Ajax的Post提交中国乱码的解决方案

    介绍: 在JQuery的Ajax POST要求,一个要求.中国的背景之中,乱码,如何解决呢? 问题简介: var regid = $('#oregion').combobox('getValue'); ...

  9. jquery-制作选项卡

    强大的jquery-制作选项卡   最近在学习jquery,特地把今天写的一个选项卡源码贴出来.只是做只是梳理,大神们请不要吐槽,如果有更好的方法,欢迎指点.谢谢. css <style> ...

  10. Android项目---常用动画

    在项目中经常会有闪屏的效果 在这里主要是通过定时器,将已经设定好的效果展现出来 /* * 2.5秒以后开始执行Runnable的run方法 */ new Handler().postDelayed(n ...