当前位置:网站首页>try-with-resources 中的一个坑,注意避让
try-with-resources 中的一个坑,注意避让
2022-06-24 19:31:00 【Java中文社群】
小伙伴们好呀,昨天复盘以前做的项目(大概有一年了),看到这个 try-catch ,又想起自己之前掉坑的这个经历 ,弄了个小 demo 给大家感受下~
问题1
一个简单的下载文件的例子。
这里会出现什么情况呢?

@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);
}
}
看完后你觉得选啥呢?
异常被全局异常处理器捕获并返回给前端。
前端收不到 response 的错误信息。

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

bug 回忆
当时和前端联调时,我发现这个异常信息前端都没有给出相应的提示,还以为是前端的问题,哈哈哈 毕竟我这代码看着也没毛病呀。
而且项目是前后端分离的,response 的 content-type 和 header 中都做了处理,前端用了 axios 去拦截这些响应,貌似还有一个 responseType: blob 这样的东东。然后刚好那会前端也不熟悉这个东西,他也以为是他前端出了问题,但是debug 的时候,看到这个 post 请求的 response 怎么是空的呢,通过 chrome 浏览器发现的。
这个时候我还很纳闷,问他说,难道你这个 前端拦截 处理掉了,不然怎么看不到(我真坑,现在真想给自己两巴掌醒醒 这尽说胡话)
后来我也觉得不对劲,就仔细去看自己的代码了,还叫了另一个同事一起看 一起猜测(中途又坑了前端一把 罪过啊……)

一两个钟过去后,我终于开窍了,想到会不会是这个 流先被关闭了 ,才导致这场闹剧的 (心里估摸着 八九不离十)
于是我便尝试性地修改下代码,拆开 try-with-resources ,改成常规的 try-catch ,并在 finally 中重写了这个流的关闭逻辑,当程序正常时,才正常关闭流,否则不关闭。
结果很顺利地就解决了这个问题……
当时也是觉得自己特蠢,第一时间居然没想到这个流被关闭的问题,还傻乎乎地怀疑这个浏览器,前端的一些写法是不是有问题,很尴尬 这么坑,,只想赶紧找个洞钻进去。。

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

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


这里给出了上面 try-with-resources 模块反编译后的代码,可以发现反编译后代码中是没有出现 finally 块的。
如果从上图看的话, try-with-resources 的作用就是下面两点了
catch Exception 时,先关闭流,再抛出异常
添加正常关闭流的代码
细心的小伙伴是不是还发现了这一行代码呢
var15.addSuppressed(var12);
这样就挖到 Throwable 来了

这个方法的作用请看
链接:https://blog.csdn.net/qiyan2012/article/details/116173807

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

到这里还不能结束 ,请接着看
问题3
这个异常还没 debug 呢,别走呀,验证一下上面 流的关闭 逻辑
在 OutputStream的 close 方法中打个断点,最后会来到 Tomcat 的 CoyoteOutputStream 中,可以看到此时的标志位 closed 和 doFlush 都是 false。

执行完 close 方法关闭后,这个 initial 从 true 变为 false ,而 closed 也变为 true。
同时,这个 堆内内存缓冲区 HeapByteBuffer 中还没来得及写入新的数据,就直接被关闭了,里面的内容还是我上一次访问留下的。

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

下面步骤有点长,就简单概括下关键点~
流关闭后,这部分代码还是照常执行的。
抛出的异常被 SpringMVC 框架的 AbstractHandlerMethodExceptionResolver 捕获,并执行 doResolveHandlerMethodException 去处理
利用 jackson 的 UTF8JsonGenerator 去进行序列化,并用 NonClosingOutputStream 对 OutputStream 进行包装。
数据写入缓冲区 (关键步骤 如下图)

可以看到流关闭后,这里 closed 也变成 true,所以自定义的信息也写不到这个缓冲区。
后面的其他 flush 操作也刷不出任何东西了。

例子的话就放到 GitHub 上了…… 直接和下期要写的例子一起放上去了
https://github.com/Java4ye/springboot-demo-4ye

总结
看完之后,你知道了我曾经犯过的一个很低级的错误 (这次脸都不要了,硬是挖了点其他内容一起写出来 )
注意流关闭的问题
谨慎使用 try-with-resources ,要考虑出异常时,这个流可不可以关闭。
同时也知道了 try-with-resources 的一些技术细节,不会生成 finally 模块(我之前的误区),而是会在异常捕获中帮我们关闭流,同时附加关闭过程的异常到最外层的异常,而且在程序的结尾增加关闭流的代码。
流关闭后,数据再也写不到缓冲区中,同时 nio 的 堆内内存缓存区 HeapByteBuffer 中的数据仍然是旧的。后面不管怎么 flush 都无法给到有效反馈信息给前端。

往期推荐
边栏推荐
- The collection of zero code enterprise application cases in various industries was officially released
- 03--- antireflective film
- Flutter 库冲突问题解决
- Filtered data analysis
- Practice of hierarchical management based on kubesphere
- 代理模式详解
- TypeScript快速入门
- PyCharm 中出现Cannot find reference ‘imread‘ in ‘__init__.py‘
- Maximum flow problem
- 如何化解35岁危机?华为云数据库首席架构师20年技术经验分享
猜你喜欢

字符串习题总结2

最大流问题

Several classes of manual transactions

Filtered data analysis

The process from troubleshooting to problem solving: the browser suddenly failed to access the web page, error code: 0x80004005, and the final positioning: "when the computer turns on the hotspot, the

架构实战营 第 6 期 毕业设计

I really can't do it. After 00, I collapsed and wanted to leave
![leetcode:515. Find the maximum value in each tree row [brainless BFS]](/img/87/1926d783fb6f8d8439213d86b5da40.png)
leetcode:515. Find the maximum value in each tree row [brainless BFS]

(to be added) games101 job 7 improvement - knowledge you need to know to realize micro surface model

Development trend and path of SaaS industry in China
随机推荐
【论】Deep learning in the COVID-19 epidemic: A deep model for urban traffic revitalization index
Balanced binary search tree
壹沓科技签约七匹狼,助力「中国男装领导者」数字化转型
Machine learning: gradient descent method
并查集+建图
Find the maximum value in each tree row [extension of one of the hierarchical traversals]
权限想要细化到按钮,怎么做?
(to be added) games101 job 7 improvement - knowledge you need to know to realize micro surface model
Reduce the pip to the specified version (upgrade the PIP through CMP and reduce it to the original version)
[精选] 多账号统一登录,你如何设计?
如何抓手机的包进行分析,Fiddler神器或许能帮到您!
Li Kou daily question - day 26 -496 Next larger element I
Xinlou: Huawei's seven-year building journey of sports health
TCP RTT测量妙计
The collection of zero code enterprise application cases in various industries was officially released
Development trend and path of SaaS industry in China
Object.defineProperty和Reflect.defineProperty的容错问题
是真干不过00后,给我卷的崩溃,想离职了...
[notes of Wu Enda] multivariable linear regression
平衡二叉搜索树


