故事
零碎里有个Excel报表导出,以前是导出xls格局,没问题。起初改成xlsx后,关上就报错了。
一开始共事还认为是用的Excel工具库不反对xlsx,但我感觉不太可能,我把不能关上的Excel文件拿来一剖析,果然有蹊跷。
不能关上的xlsx文件开端多了一个字符串{reponseCode:"000000",reponseMsg:"胜利"}
,之前xls格局能关上可能是因为xls谬误兼容性比拟好。
多这个字符串的起因是导出报表伪代码如下
@POST@Path("/download")public String download(){ WorkBook wb = generateWorkBook(); wb.save(response.getOutputStream()); return {reponseCode:"000000",reponseMsg:"胜利"};}
最开始的共事之所以返回String我猜是想着出错时返回一些谬误提醒。但其实真的出错时间接往外抛异样即可。所以共事将代码改成
@POST@Path("/download")public void download(){ WorkBook wb = generateWorkBook(); wb.save(response.getOutputStream());}
不要在操作outputStream时同时在办法返回字符串就好。
问题
这个故事引出了本篇文章。
事实上网上大把文件下载的示例代码都是这样写的。返回值为void,而后间接操作OutputStream。
但我认为这切实不是一个好的实际。
我认为无论如何不应该去操作outputStream。
如果要返回文件,就应该显式地申明返回值。
思考
函数的输入输出,十分直观。Controller的接口实质也是一个函数。
// 例子1public String sayHello(String uername){ return "hello " + username;}// 不言而喻,假如输出"张三",那么函数的返回是"hello 张三",// 如果是在controller里,那客户端收到的报文是"hello 张三"
十分直观的输出、输入对吧,
惯例的rest接口不会有人想着要间接入操作outputStream,对吧?
尬来一波,如果有人非要!
如果你在controller里看到上面的代码,你认为这个函数的输入是什么?客户端收到的返回又是什么?
// 例子2pubilc String sayHello(String username){ response.getOutputStream().write("world".getBytes()); return "hello " + username;}// 同样输出"张三",尽管这个函数的返回值是 "hello 张三",// 然而对于http申请来说,客户端收到的返回是 "world hello 张三"// 看看本文结尾的例子,尽管函数自身的返回值仅仅是一个JSON字符串,// 然而客户端收到的返回却是【excel文件+JSON字符串】。
持续往下看,加个 response.getOutputStream().close()你认为这个函数的输入是什么?客户端收到的返回又是什么?
// 例子3pubilc String sayHello(String username){ response.getOutputStream().write("world".getBytes()); reponse.getOutputStream().close(); return "hello " + username;}// 同样输出"张三",尽管这个函数的返回值是 "hello 张三",// 然而对于http申请来说,客户端收到的返回是 "world"// 因为outputStream曾经被敞开了,返回值不能再写入到outputStream里// PS: springmvc体现不一样,springmvc对request和response包了一层wrapper,// 在springmvc下操作`outputStream.close()`实际上是执行了一个空办法,// 所以springmvc下返回和例子2一样
请问:这两个例子,是不是让你感觉很凌乱,很不直观?
再次回到本文的观点,为什么不要去操作outputStream?
没有显著的益处。
诚然,惯例 restful 的接口咱们间接return数据比较简单,所以不会有人自找麻烦去操作outputStream。然而对于文件,用返回值的模式是给你带了很大麻烦吗?间接操作outputStream是给你带来了很大的便当吗?
与惯例的思维逻辑相悖。
办法返回体申明为void即是表明该办法无返回值,然而理论你又返回了一个文件。
带来凌乱。
如上述的3个例子,当有老手开发在操作outputStream的同时还申明了返回值,可能还调用了flush或close等办法时,会有一些意想不到的体现。
可能带来未知的问题。
不同的框架有不同的实现,不是所有人都会去钻研框架的源码。如上述例子3的代码在springmvc和jersey下体现就是不统一。
所以,为什么就那么执着地要去间接操作outputStream呢?
论断
无论如何不应该在Controller里间接操作outputStream。
那以常见的文件下载需要来说,应该怎么写文件下载呢?
不举荐:间接操作outputStream,同时办法返回值申明为void。
举荐:间接返回文件流
// jersey的写法
public Response downloadExcel(){ StreamingOutput stream = 二进制文件内容; return Response .ok(stream, MediaType.APPLICATION_OCTET_STREAM) .header("content-disposition","attachment; filename=xx.xlsx") .build();}
springmvc的示例
@Controllerpublic class DownloadController { @GetMapping public ResponseEntity<Resource> downloadPdf() { FileSystemResource resource = new FileSystemResource("/xx.xlsx"); ContentDisposition disposition = ContentDisposition .inline() // or .attachment() .filename(resource.getFilename()) .build(); headers.setContentDisposition(disposition); return new ResponseEntity<>(resource, headers, HttpStatus.OK); }}