大数跨境
0
0

Spring这个大坑要注意啦!会导致资源泄漏

Spring这个大坑要注意啦!会导致资源泄漏 Spring全家桶实战案例
2024-05-19
0
导读:Spring这个大坑要注意啦!赶紧检查代码

环境:SpringBoot3.2.5



1. 复现坑

这是一个关于使用Resource下载文件引发的问题。示例代码如下:

@RestController@RequestMapping("/download")public class StreamDownloadController {
@GetMapping("")  public ResponseEntity<Resource> download() throws Exception {    // 故意写错    String contentType = "textplain" ;   // 确保文件是存在的 InputStream is = new FileInputStream(new File("d:\\1.txt")) ;    Resource resource = new InputStreamResource(is) ;    return ResponseEntity.ok()           .header(HttpHeaders.CONTENT_TYPE, contentType)           .body(resource) ; }

上面代码通过ResponseEntity实现文件的下载,设置的Content-Type header故意写错,正确应该是格式 "*/*"。调用上面接口后,程序报错:

错误信息就是我们这是的Content-Type错误。仅这个错误还好,关键出现错误后会引发资源泄漏问题,也就是说我们的程序还会继续占用这个1.txt文件资源,通过删除该文件可以看到提示:

试想如果我们这个接口是在高并发环境下,那么定会引起操作系统中的文件句柄被耗尽。引起原因是没有关闭文件流。

2. 解决方案

2.1 错误的解决方案

可能你会说,用try-with-resources,下面将代码改为这种方式

@GetMapping("")public ResponseEntity<Resource> download() throws Exception {  try (InputStream is = new FileInputStream(new File("d:\\1.txt"))) {    Resource resource = new InputStreamResource(is) ;    return ResponseEntity.ok()            .header(HttpHeaders.CONTENT_TYPE, "text/plain")            .body(resource) ;  }}

注意:上面代码处理没有问题,Content-Type也是正确的。

报了其它的错误“Stream Closed”。这需要你对SpringMVC底层原理有了解,简单说下Controller接口调用:

  1. 调用接口download方法,获取到返回值

    这时候这个download方法已经执行完了,try-with执行完文件流当然要关闭了。

  2. 得到上一步的返回值,通过结果类型找到对应的HandlerMethodReturnValueHandler处理器,输出结果。这时候才真正的去读取文件流,但是文件流已经在上一步中关闭。所以就出现了上面的错误。


2.2 正确的解决方案

  • 方法1

这种方法必须放弃使用ResponseEntity返回值方式,如下代码:

@GetMapping("")public void download(HttpServletResponse response) throws Exception {  try (InputStream is = new FileInputStream(new File("d:\\1.txt"))) {    response.setContentType("xxxooo") ;    is.transferTo(response.getOutputStream()) ;  }}

这种方法流打开及读取发送客户端都在try中,不会出现上面的问题。

  • 方法2

如果你的返回值必须是ResponseEntity那么,只能通过如下方式实现:

@GetMapping("")public ResponseEntity<Resource> download3() throws Exception {  Resource resource = new AbstractResource() {    @Override    public InputStream getInputStream() throws IOException {      // 只有真正的在进行读取的时候才打开文件流      InputStream is = new FileInputStream(new File("d:\\1.txt")) ;      return is ;    }    @Override    public String getDescription() {      return "下载文件" ;    }  };  return ResponseEntity.ok()          .header(HttpHeaders.CONTENT_TYPE, "xxxooo")          .body(resource) ;}

但是这种方式编码稍有点麻烦,需要自定义Resource实现。

  • 方法3

该方法需要升级Spring版本,才发布没几天的6.1.7,在该版本中对InputStreamResource增加了一个构造方法,如下:

public InputStreamResource(InputStreamSource inputStreamSource)

参数可以作为lambda表达式提供,该表达式可以延迟检索实际的InputStream

接口改造如下

@GetMapping("")public ResponseEntity<Resource> download() throws Exception {  Resource resource = new InputStreamResource(    new FileSystemResource(new File("d:\\1.txt"))  ) ;  return ResponseEntity.ok()         .header(HttpHeaders.CONTENT_TYPE, "xxxooo")         .body(resource) ;}

该方法的原理与上面一样。

注意:我使用的SpringBoot版本是3.2.5对应Spring版本是6.1.6,所以你进行如下配置:

<spring-framework.version>6.1.7</spring-framework.version>

这样能替换默认的版本。

以上是本篇文章的全部内容,如对你有帮助就请作者吃个棒棒糖🍭。

推荐文章

Spring注入还可以这样玩!涨知识了

SpringBoot项目中这10个开发技巧你都知道吗?

线上故障!手贱导致事务失效

Spring6.2这几个新功能及优化太强了

实战案例SpringBoot整合Seata AT模式实现分布式事务【超详细】

SpringBoot3使用虚拟线程一定要小心了

快速体验Spring AI 聊天对话&文生图&文生语音

SpringBoot优雅地定制JSON响应数据

Spring6.2震撼来袭,多线程实例化Bean应用启动速度飙升!

SpringBoot整合Flink CDC,实时追踪数据变动,无缝同步至Redis

【高效开发】使用Spring Data JPA的QBE功能,轻松构建查询条件

两种方式实现SpringBoot外部配置实时刷新最佳实践

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读195
粉丝0
内容832