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. Asp.net MVC + EF + Spring.Net 项目实践(目录)

    用4篇博客来搭一个MVC的框架,可能对初学者会有一些帮助,大家共勉吧.我觉得对于中小型项目,这个框架可能还是有一定的用处的,希望能够帮助到一些人. Asp.net MVC + EF + Spring. ...

  2. 分布式服务弹性框架“Hystrix”实践与源码研究(一)

    文章初衷 为了应对将来在线(特别是无线端)业务量的成倍增长,后端服务的分布式化程度需要不断提高,对于服务的延迟和容错管理将面临更大挑战,公司框架和开源团队选择内部推广Netflix的Hystrix,一 ...

  3. 将已有的工程项目添加到Xcode到Git管理中

    在Xcode中创建工程的时候,我们很容易的可以将新创建的工程添加到Git中,如图: 但是如果是本地已经有的工程,那该如何添加到Git中呢? 首先终端进入到该工程的目录. 然后: git init gi ...

  4. 阐述linux IPC(五岁以下儿童):system V共享内存

    [版权声明:尊重原创.转载请保留源:blog.csdn.net/shallnet 要么 .../gentleliu,文章学习交流,不用于商业用途]         system V共享内存和posix ...

  5. JS获取字符串实际长度

    JS中默认中文字符长度和其它字符长度计算方法是一样的,但某些情况下我们需要获取中文字符串的实际长度,代码如下: function strLength(str) { var realLength = 0 ...

  6. Mysql高级之权限检查原理

    原文:Mysql高级之权限检查原理 用户进行数据库操作分为两步: 1 是否有权限连接,根据host,name,password: 2 是否有权限进行CURD: 图示解说: 关于用户权限在哪里进行存放? ...

  7. DDD(领域驱动设计)应对具体业务场景,如何聚焦 Domain Model(领域模型)?

    DDD(领域驱动设计)应对具体业务场景,如何聚焦 Domain Model(领域模型)? 阅读目录: 问题根源是什么? <领域驱动设计-软件核心复杂性应对之道>分层概念 Repositor ...

  8. 【推荐分享】Python电子书,视频教程(Let's Python系列视频教程等)(百度网盘)

    资源都放在百度网盘里了. Python视频教程(Python Django视频教程全集—台湾辅仁大学):http://pan.baidu.com/s/1dDgiWIt Python视频教程(let's ...

  9. Django是Python下的一款网络服务器框架

    被解放的姜戈01 初试天涯   Django是Python下的一款网络服务器框架.Python下有许多款不同的框架.Django是重量级选手中最有代表性的一位.许多成功的网站和APP都基于Django ...

  10. iOS基础 - NSURLSession

    使用URLSession所有的网络访问都是有缓存的,缓存文件自动保存在tmp文件夹中,URLSession本身实现的时候,就是少量多次的! l 使用defaultSessionConfiguratio ...