前言
项目使用 @ControllerAdvice
来做异常的兜底处理。但是实现一个下载需求的时候,即使服务端报错抛异常,也无法提供前端良好的响应,需要等文件下下来后,客户才发现是损坏的。
- 当前情况 (服务端报错依旧让前端接收文件)
- 期望情况
ControllerAdvice 代码
写个demo
package com.james.mybatislearncache;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@ControllerAdvice(annotations = {RestController.class, Controller.class})
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public Result exceptionHandler(Exception ex) {
System.out.println("抛异常了:" + ex);
return new Result<>().fail("服务器异常");
}
}
try-with-resource 代码
- controller 层
@PostMapping("/donwload/try-with-resource")
public void testTryWithResource(HttpServletResponse response) throws Exception {
downloadTryWithResource(response, "C:\\Users\\James\\Desktop\\test\\");
}
- try-with-resource 将用于传输文件的 response 包裹住了
public void downloadTryWithResource(HttpServletResponse response, String sourceFilePath) throws Exception {
// 把
try (OutputStream os = response.getOutputStream();) {
byte[] data = createZip(sourceFilePath);
// 省略中间代码,会抛出异常
// org.apache.commons.io 下的工具类
IOUtils.write(data, os);
}
}
try-with-resource 作用 response 造成的副作用
debug进Spring MVC 的代码里面
这里就不示范了,具体就是从 org.springframework.web.servlet.DispatcherServlet#doDispatch 方法跟进去,代码层次有点深,需要多次调试缩小观察范围。
mvc想要渲染 ControllerAdvice 的结果时, 发现response已经被关闭了
org.apache.catalina.connector.OutputBuffer#writeBytes(byte[], int, int)
结果分析
public void downloadTryWithResource(HttpServletResponse response, String sourceFilePath) throws Exception {
try (OutputStream os = response.getOutputStream();) {
byte[] data = createZip(sourceFilePath);
// 省略中间代码,会抛出异常
// org.apache.commons.io 下的工具类
IOUtils.write(data, os);
}
}
@ControllerAdvice(annotations = {RestController.class, Controller.class})
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public Result exceptionHandler(Exception ex) {
System.out.println("抛异常了:" + ex);
return new Result<>().fail("服务器异常");
}
}
try-with-resource 遇到异常后把response关闭了,没有顾及Spring MVC 中 ControllerAdvice 还需要借 response 把响应写到客户端。
后记
- 最佳实践鼓励用 try-with-resource 处理流,但是还要考虑其他框架要不要用流。
- Spring MVC 会自动管理 response 的流,不需要自己手动关闭