前几天项目上线,就是把MVC框架由
Spring MVC换成Restlet
,然后客户说原有的TXT文件导出功能报500了,而且生产上没有报错日志,而后面更有客户说某些的TXT文件导出功能是可行的。经过场景复现,最终推导出这个TXT功能
大文件导出是正常的,而小文件就会失败
。
针对这种场景,当时就猜测是由于小文件在写入缓冲区后到响应给浏览器时出现了异常,最终经过层层的堆栈分析,发现Restlet框架是用的
ServletOutputStream对象
去写信息,而TXT导出的功能类用的是
PrintWriter对象
去写出,而在Response对象的定义中,只能用一种方式去进行写出响应信息。而为什么这样定义,这是因为ServletOutputStream用的是
OutputBuffer对象的字节流bb数组
,而PrintWriter用的是
OutputBuffer对象的cb数组
,所以这两种方式不能同时使用。
2、原因查找
2.1 小文件失败
我们先来看Response对象获取PrintWriter与ServletOutputStream的两个方法
可以看出,这两个方法是互斥的,同时从上面两个方法可以看出,他们具体的数据处理都委托给了
OutputBuffer对象,具体细节我们可以看看CoyoteOutputStream与CoyoteWriter的write方法。
CoyoteWriter的wrtie方法:
CoyoteOutputStream的wrtie方法:
这里的ob就是指的OutputBuffer,然后我们追踪到OutputBuffer里,最终发现他对于这两种方式分别用了两个不同的数据来存储
具体OutputBuffer在写入数据时做了什么处理,我们就不去深究了,至此我们可以清晰的明白,
ServletOutputStream与PrintWriter不能同时使用的原因是因为他们所使用的数据缓冲区对象不同
。
而在Restlet框架设计中,他是用ServletCall
2.2 大文件可行
出现这个现象是因为OutputBuffer的缓冲区大小问题,若需要写入的数据大于OutputBuffer的缓冲区大小,那么OutputBuffer会提前把缓冲区内容提前通过SocketOutputStream响应给Client
在write方法里可以看到,他会检查cb缓冲区是否已满,若已满则提前把内容刷出去。所以当所导出的文件大于缓冲区大小时,就可以正常的导出。
所以在使用Response对象的PrintWriter对象与ServletOutputStream对象时,一定需要注意他们是不能同时使用的,避免此类的bug发生