try-with-resources 中的一个坑,注意避让

小伙伴们好呀,昨天复盘以前做的项目(大概有一年了),看到这个  try-catch ,又想起自己之前掉坑的这个经历 ,弄了个小 demo 给大家感受下~  😄

问题1

一个简单的下载文件的例子。

这里会出现什么情况呢

131680e6ffc8fdffc30a80351b8b6562.png
@GetMapping("/download")public void downloadFile(HttpServletResponse response) throws Exception {String resourcePath = "/java4ye.txt";URL resource = DemoApplication.class.getResource(resourcePath);String path = resource.getPath().replace("%20", " ");try( ServletOutputStream outputStream  = response.getOutputStream();FileInputStream fileInputStream = new FileInputStream(path)) {byte[] bytes = new byte[8192];ByteArrayOutputStream baos = new ByteArrayOutputStream();int len = 0;while ((len = fileInputStream.read(bytes)) != -1) {baos.write(bytes, 0, len);}String fileName = "java4ye.txt";//            response.setHeader("content-type", "application/octet-stream;charset=UTF-8");
//            response.setContentType("application/octet-stream");
//            response.setHeader("Access-Control-Expose-Headers", "File-Name");
//            response.setHeader("File-Name", fileName);// 异常int i = 1/0;response.setHeader("Content-Disposition", "attachment;filename=" + fileName);outputStream.write(baos.toByteArray());} catch (Exception e) {throw new DownloadException(e);}}
aa7e6730105946782ee19c4d6ae5af08.png

看完后你觉得选啥呢?

  1. 异常被全局异常处理器捕获并返回给前端。

  2. 前端收不到 response 的错误信息。

99737f03804a0f5360be79636ac39600.png

答案当然是 2 啦,哈哈 正常的话就不会写出来了 😝

01c92b5fbf7b70552b39b007a4124223.png

bug 回忆

当时和前端联调时,我发现这个异常信息前端都没有给出相应的提示,还以为是前端的问题,哈哈哈 毕竟我这代码看着也没毛病呀。😄

而且项目是前后端分离的,response 的 content-type 和 header 中都做了处理,前端用了 axios 去拦截这些响应,貌似还有一个 responseType: blob 这样的东东。然后刚好那会前端也不熟悉这个东西,他也以为是他前端出了问题,但是debug 的时候,看到这个 post 请求的 response 怎么是空的呢,通过 chrome 浏览器发现的。

这个时候我还很纳闷,问他说,难道你这个 前端拦截 处理掉了,不然怎么看不到😂(我真坑🕳,现在真想给自己两巴掌醒醒😂 这尽说胡话😂)

后来我也觉得不对劲,就仔细去看自己的代码了,还叫了另一个同事一起看 🐷  一起猜测(中途又坑了前端一把 罪过啊……😂)

784c9a58e725238bfd88e90fb5c55233.png

一两个钟过去后,我终于开窍了,想到会不会是这个 流先被关闭了 ,才导致这场闹剧的😱 (心里估摸着 八九不离十)

于是我便尝试性地修改下代码,拆开 try-with-resources ,改成常规的  try-catch ,并在 finally 中重写了这个流的关闭逻辑,当程序正常时,才正常关闭流,否则不关闭。

结果很顺利地就解决了这个问题…… 😅

当时也是觉得自己特蠢,第一时间居然没想到这个流被关闭的问题,还傻乎乎地怀疑这个浏览器,前端的一些写法是不是有问题,很尴尬😅 这么坑,,只想赶紧找个洞钻进去。。

4ef1ad242f5dd8827d3a615866c93153.png

再次看到这个代码,觉得里面应该还有东西可以细挖出来的,于是便有了这文~ 🐖(公开处刑,引以为戒)

2fb216726a45ccab760a097b543f2785.png

问题2

你有看过  try-with-resources  和  try-catch 编译后和反编译出来的代码吗? 有对比过他们的不同吗~

f7d410ce89226fc83044aeda70d3d8c6.png
整体
f191cbc4c7b86e53c191fe312868147b.png
细节

这里给出了上面  try-with-resources  模块反编译后的代码,可以发现反编译后代码中是没有出现 finally 块的。

如果从上图看的话, try-with-resources  的作用就是下面两点了

  1. catch Exception 时,先关闭流,再抛出异常

  2. 添加正常关闭流的代码

细心的小伙伴是不是还发现了这一行代码呢 😄

var15.addSuppressed(var12);

这样就挖到  Throwable 来了🐖

053d7372faa8b20493446d84c8b19f32.png
image-20220413230827492

这个方法的作用请看 👇

链接:https://blog.csdn.net/qiyan2012/article/details/116173807

c6fa54d3e7fbb5427cd015a1a9032345.png

大概意思就是把异常挂到最外层的异常中去 👍 ,不过从方法的注释上可以知道,这个一般都是  try-with-resources  偷偷帮我们做的。

5053b8d4bcd78bbefce034393fb389d5.png

到这里还不能结束 ,请接着看 😄

问题3

这个异常还没 debug 呢,别走呀,验证一下上面 流的关闭 逻辑🐖

在 OutputStream的 close 方法中打个断点,最后会来到 Tomcat 的 CoyoteOutputStream 中,可以看到此时的标志位 closed 和 doFlush 都是 false。

e8b03b4bc24dcfe7e8943bd6ad9dd147.png

执行完 close 方法关闭后,这个 initial 从 true 变为 false ,而 closed 也变为 true。

同时,这个 堆内内存缓冲区 HeapByteBuffer 中还没来得及写入新的数据,就直接被关闭了,里面的内容还是我上一次访问留下的。🐖

0439156fd02e8e1f268d591004e9cf76.png

关闭流后,才去捕获这个异常,这和我们反编译后看到的代码逻辑是一致的

0faca6e84f9201099abd9b9c5964eeb2.png

下面步骤有点长,就简单概括下关键点~ 👇

流关闭后,这部分代码还是照常执行的。

  1. 抛出的异常被 SpringMVC 框架的 AbstractHandlerMethodExceptionResolver 捕获,并执行 doResolveHandlerMethodException 去处理

  2. 利用 jackson 的 UTF8JsonGenerator 去进行序列化,并用 NonClosingOutputStream  对 OutputStream 进行包装。

  3. 数据写入缓冲区 (关键步骤 如下图👇)

a08f70865c4f1dd90ebf7a88917d443a.png

可以看到流关闭后,这里 closed 也变成 true,所以自定义的信息也写不到这个缓冲区。

后面的其他 flush 操作也刷不出任何东西了。

f76b80e8d6e45ecfbf4775b7e235ec61.png

例子的话就放到 GitHub 上了……  直接和下期要写的例子一起放上去了🐖

https://github.com/Java4ye/springboot-demo-4ye

580da2e36f5360830655d74fd48d7f44.png

总结

看完之后,你知道了我曾经犯过的一个很低级的错误😂 (这次脸都不要了,硬是挖了点其他内容一起写出来 🐷)

  1. 注意流关闭的问题

  2. 谨慎使用  try-with-resources ,要考虑出异常时,这个流可不可以关闭。

  3. 同时也知道了  try-with-resources 的一些技术细节,不会生成 finally 模块(我之前的误区🐖),而是会在异常捕获中帮我们关闭流,同时附加关闭过程的异常到最外层的异常,而且在程序的结尾增加关闭流的代码。

  4. 流关闭后,数据再也写不到缓冲区中,同时 nio 的 堆内内存缓存区 HeapByteBuffer 中的数据仍然是旧的。后面不管怎么 flush 都无法给到有效反馈信息给前端。

287be59038b47ba084b86c6ac1d4f897.gif

往期推荐

346b607aed2556fef617918a600ad69c.png

33岁程序员的年中总结


5e559f09b269c2c0399ba0663239fa3c.png

面渣逆袭:MySQL六十六问!建议收藏


673b29ab1be47fe973667b507724fb7d.png

实战:10 种实现延迟任务的方法,附代码!


c22ff14e064454e0f2b93a94357726cd.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544099.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第 二 十 八 天 :LB 负 载 均 衡 搭 建 之 LVS

小Q:抱怨,是一种负能量,犹如搬起石头砸自己的脚,与人无益,于己不利,于事无补 前面我们介绍了HA高可用集群,今天我们来了解下LB负载均衡集群,在学习完基本的搭建后,在扩展…

一个依赖搞定Spring Boot 配置文件脱敏

经常会遇到这样一种情况:项目的配置文件中总有一些敏感信息,比如数据源的url、用户名、密码....这些信息一旦被暴露那么整个数据库都将会被泄漏,那么如何将这些配置隐藏呢?今天介绍一种方案,让你在无感知的情况下实现配…

js ‘use strict’详解

2019独角兽企业重金招聘Python工程师标准>>> 一、概述 除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict mode)。顾名思义,这种模式使得Javascript在更严格的条件下运行。 …

如何优雅的写 Controller 层代码?

本篇主要要介绍的就是controller层的处理,一个完整的后端请求由4部分组成:1. 接口地址(也就是URL地址)、2. 请求方式(一般就是get、set,当然还有put、delete)、3. 请求数据(request,有head跟body)、4. 响应数据(response)本篇将解…

面试突击60:什么情况会导致 MySQL 索引失效?

作者 | 磊哥来源 | Java面试真题解析(ID:aimianshi666)转载请联系授权(微信ID:GG_Stone)为了验证 MySQL 中哪些情况下会导致索引失效,我们可以借助 explain 执行计划来分析索引失效的具体场景。…

使用PHP建立SVN的远程钩子,使用exec命令自动更新SVN的代码

2019独角兽企业重金招聘Python工程师标准>>> 本操作需要使用到php执行sudo命令的权限,相关设置可以参考:apache/Nginx下的PHP/Ruby执行sudo权限的系统命令 通过Svn的钩子功能,可以在我们执行SVN操作时,同时自动执行一些…

Java 中 for 和 foreach 哪个性能高?

作为程序员每天除了写很多 if else 之外,写的最多的也包含 for 循环了,都知道我们 Java 中常用的 for 循环有两种方式,一种是使用 for loop,另一种是使用 foreach,那如果问你,这两种方式哪一种效率最高&…

阿里出品,SpringBoot自动化部署神器!

最近发现一款阿里出品的IDEA插件CloudToolkit,不仅支持直接打包应用部署到远程服务器上,而且还能当终端工具使用。试用了一把这个插件,非常不错,推荐给大家!装上这个插件,IDEA一站式开发又近了一步&#xf…

聊聊异步编程的 7 种实现方式

最近有很多小伙伴给我留言,能不能总结下异步编程,今天就和大家简单聊聊这个话题。早期的系统是同步的,容易理解,我们来看个例子同步编程当用户创建一笔电商交易订单时,要经历的业务逻辑流程还是很长的,每一…

二进制补码乘法除法_二进制乘法和除法

二进制补码乘法除法1)二进制乘法 (1) Binary Multiplication) Binary numbers can be multiplied using two methods, 二进制数可以使用两种方法相乘, Paper method: Paper method is similar to multiplication of decimal numbers on paper. 纸张方法&#xff1a…

控制JSP头部引入外部文件编译后在第一行

2019独角兽企业重金招聘Python工程师标准>>> 一.错误引入方法 假设当前需要在JSP页面输出xml格式数据,需要引入以下外部文件,通过以下的方式来引入则无法正常输出数据: 访问页面会报错误:xml的声明不在文档的第一行 看…

4种常见的缓存模式,你都知道吗?

概述 在系统架构中,缓存可谓提供系统性能的最简单方法之一,稍微有点开发经验的同学必然会与缓存打过交道,最起码也实践过。如果使用得当,缓存可以减少响应时间、减少数据库负载以及节省成本。但如果缓存使用不当,则可能…

面试突击63:distinct 和 group by有什么区别?

作者 | 磊哥来源 | Java面试真题解析(ID:aimianshi666)转载请联系授权(微信ID:GG_Stone)在 MySQL 中,最常见的去重方法有两个:使用 distinct 或使用 group by,那它们有什…

从20s优化到500ms,我用了这三招

前言接口性能问题,对于从事后端开发的同学来说,是一个绕不开的话题。想要优化一个接口的性能,需要从多个方面着手。本文将接着接口性能优化这个话题,从实战的角度出发,聊聊我是如何优化一个慢查询接口的。上周我优化了…

面试拆解:系统上线后CPU急速飙升,该怎么排查?

上次面试官问了个问题:应用上线后Cpu使用率飙升如何排查?其实这是个很常见的问题,也非常简单,那既然如此我为什么还要写呢?因为上次回答的时候我忘记将线程PID转换成16进制的命令了。所以我决定再重温一遍这个问题&…

MySQL 死锁了,怎么办?

作者:小林coding提纲如下:正文有个业务主要逻辑就是新增订单、修改订单、查询订单等操作。然后因为订单是不能重复的,所以当时在新增订单的时候做了幂等性校验,做法就是在新增订单记录之前,先通过 select ... for upda…

10 张图搞懂服务注册发现机制

在微服务架构或分布式环境下,服务注册与发现技术不可或缺,这也是程序员进阶之路必须要掌握的核心技术之一,本文通过图解的方式带领大家轻轻松松掌握。引入服务注册与发现组件的原因先来看一个问题,假如现在我们要做一个商城项目&a…

ASP.NET 5 Beta8 已经发布

Microsoft ASP.NET and Web Tools 2015 (Beta8) http://www.microsoft.com/en-us/download/details.aspx?id49442 .net core 完成了98%,绝大部分类库完成了跨平台开发,已经基本可用,下一版本为RC,发布时间为12月,将可…

面试突击65:HTTPS有什么优点?说一下它的执行流程?

作者 | 磊哥来源 | Java面试真题解析(ID:aimianshi666)转载请联系授权(微信ID:GG_Stone)说到 HTTPS 相信大部分人都是不陌生,因为目前我们使用的绝大数网站都是基于 HTTPS 的,比如以…

Cell.reuseIdentifier 指什么

Cell.reuseIdentifier 指的是 默认为空,如果不定义,在执行 [_tableView registerNib:templateCellNib forCellReuseIdentifier:_templateCell.reuseIdentifier]; 时,提示 must pass a valid reuse identifier to -[UITableView registerNib:f…