如何使用 StreamingResponseBody 和 try-with-resources?
How to use StreamingResponseBody and try-with-resources?
我想动态创建一个 Excel sheet 并 return 给客户端。我想使用 StreamingResponseBody
作为 return 类型以节省一些 RAM。我还使用 try-with-resources
自动关闭 workbook
。这是代码。
@RestController
public class ExcelController {
@GetMapping("/hello")
public ResponseEntity<StreamingResponseBody> excel() throws IOException {
try (var workbook = new XSSFWorkbook();) {
workbook.createSheet("hello world");
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=data.xlsx")
.body(workbook::write);
}
}
}
不幸的是,这不起作用,我收到以下错误消息。
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
java.io.IOException: Cannot write data, document seems to have been closed already
at org.apache.poi.ooxml.POIXMLDocument.write(POIXMLDocument.java:215) ~[poi-ooxml-5.1.0.jar:5.1.0]
at org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler$StreamingResponseBodyTask.call(StreamingResponseBodyReturnValueHandler.java:111) ~[spring-webmvc-5.3.13.jar:5.3.13]
at org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler$StreamingResponseBodyTask.call(StreamingResponseBodyReturnValueHandler.java:98) ~[spring-webmvc-5.3.13.jar:5.3.13]
at org.springframework.web.context.request.async.WebAsyncManager.lambda$startCallableProcessing(WebAsyncManager.java:337) ~[spring-web-5.3.13.jar:5.3.13]
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) ~[na:na]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
我已经尝试使用 StreamingResponseBody
所述的自定义 TaskExecutor,但这并没有真正帮助。最后我找到了一个可行的解决方案,但我想知道为什么第一个解决方案包括 try-with-resources
和方法参考不起作用。
@RestController
public class ExcelController {
@GetMapping("/hello")
public ResponseEntity<StreamingResponseBody> excel() throws IOException {
var workbook = new XSSFWorkbook();
workbook.createSheet("hello world");
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=data.xlsx")
.body(
out -> {
workbook.write(out);
workbook.close();
});
}
}
非常感谢您的帮助!谢谢
因为 StreamingResponseBody
是异步工作的,并且由于您的 try-with-resources 在该部分之外,所以资源可能已经关闭(我怀疑 99.9% 的情况?)。要修复 StreamingResponseBody
.
中的所有内容
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=data.xlsx")
.body(
out -> {
try (var workbook = new XSSFWorkbook()) {
workbook.createSheet("hello world");
workbook.write(out);
}
});
}
为了使其更具可读性(以及我不喜欢多行 lambda 的事实),您当然也可以将该代码移动到一个方法中并从您的 lambda 中调用它。
private void writeWorkbook(OutputStream out) {
try (var workbook = new XSSFWorkbook()) {
workbook.createSheet("hello world");
workbook.write(out);
}
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=data.xlsx")
.body( this::writeWorkbook);
}
这将在适当的时候打开(和关闭)Workbook
。
我想动态创建一个 Excel sheet 并 return 给客户端。我想使用 StreamingResponseBody
作为 return 类型以节省一些 RAM。我还使用 try-with-resources
自动关闭 workbook
。这是代码。
@RestController
public class ExcelController {
@GetMapping("/hello")
public ResponseEntity<StreamingResponseBody> excel() throws IOException {
try (var workbook = new XSSFWorkbook();) {
workbook.createSheet("hello world");
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=data.xlsx")
.body(workbook::write);
}
}
}
不幸的是,这不起作用,我收到以下错误消息。
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
java.io.IOException: Cannot write data, document seems to have been closed already
at org.apache.poi.ooxml.POIXMLDocument.write(POIXMLDocument.java:215) ~[poi-ooxml-5.1.0.jar:5.1.0]
at org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler$StreamingResponseBodyTask.call(StreamingResponseBodyReturnValueHandler.java:111) ~[spring-webmvc-5.3.13.jar:5.3.13]
at org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler$StreamingResponseBodyTask.call(StreamingResponseBodyReturnValueHandler.java:98) ~[spring-webmvc-5.3.13.jar:5.3.13]
at org.springframework.web.context.request.async.WebAsyncManager.lambda$startCallableProcessing(WebAsyncManager.java:337) ~[spring-web-5.3.13.jar:5.3.13]
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) ~[na:na]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
我已经尝试使用 StreamingResponseBody
所述的自定义 TaskExecutor,但这并没有真正帮助。最后我找到了一个可行的解决方案,但我想知道为什么第一个解决方案包括 try-with-resources
和方法参考不起作用。
@RestController
public class ExcelController {
@GetMapping("/hello")
public ResponseEntity<StreamingResponseBody> excel() throws IOException {
var workbook = new XSSFWorkbook();
workbook.createSheet("hello world");
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=data.xlsx")
.body(
out -> {
workbook.write(out);
workbook.close();
});
}
}
非常感谢您的帮助!谢谢
因为 StreamingResponseBody
是异步工作的,并且由于您的 try-with-resources 在该部分之外,所以资源可能已经关闭(我怀疑 99.9% 的情况?)。要修复 StreamingResponseBody
.
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=data.xlsx")
.body(
out -> {
try (var workbook = new XSSFWorkbook()) {
workbook.createSheet("hello world");
workbook.write(out);
}
});
}
为了使其更具可读性(以及我不喜欢多行 lambda 的事实),您当然也可以将该代码移动到一个方法中并从您的 lambda 中调用它。
private void writeWorkbook(OutputStream out) {
try (var workbook = new XSSFWorkbook()) {
workbook.createSheet("hello world");
workbook.write(out);
}
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=data.xlsx")
.body( this::writeWorkbook);
}
这将在适当的时候打开(和关闭)Workbook
。