Liferay文件下载报错:Cannot obtain Writer because OutputStream is already in use
解决Liferay文件下载时的
Cannot obtain Writer because OutputStream is already in use错误 先看你遇到的错误日志:
05:03:35,867 ERROR [[ACTIVE] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)'][PortletRequestDispatcherImpl:115] org.springframework.web.util.NestedServletException: View rendering failed; nested exception is java.lang.IllegalStateException: Cannot obtain Writer because OutputStream is already in use org.springframework.web.util.NestedServletException: View rendering failed; nested exception is java.lang.IllegalStateException: Cannot obtain Writer because OutputStream is already in use at org.springframework.web.servlet.ViewRendererServlet.processRequest(ViewRendererServlet.java:96) at org.springframework.web.servlet.ViewRendererServlet.doGet(ViewRendererServlet.java:67) Caused by: java.lang.IllegalStateException: Cannot obtain Writer because OutputStream is already in use at com.liferay.portlet.MimeResponseImpl.getWriter(MimeResponseImpl.java:90) at com.liferay.portlet.PortletServletResponse.getWriter(PortletServletResponse.java:207) at com.netcracker.portal.framework.spring.templates.view.SoyDataView.renderMergedOutputModel(SoyDataView.java:93)
再看你的代码,问题其实很明确——你在获取PortletOutputStream之前调用了resourceResponse.flushBuffer(),这直接导致了响应输出方式的冲突。
问题根源
根据Portlet规范,一个响应只能使用Writer或者OutputStream其中一种输出方式,不能同时混用。当你调用flushBuffer()时,Liferay会提前提交响应头,甚至可能因为内部的视图渲染逻辑(比如你日志里的SoyDataView)已经初始化了Writer,这时候再去获取OutputStream就会抛出这个IllegalStateException。
另外你的代码还有两个小问题:
- 文件名里带了空格,可能导致部分浏览器解析文件名出错
- 手动关闭流的方式不够安全,万一出现异常可能导致资源泄漏
修复后的代码
String defaultFileName = "hello.pdf"; resourceResponse.setContentType("application/octet-stream"); // 用双引号包裹文件名,避免空格/特殊字符解析问题 resourceResponse.addProperty( GenericServiceTransport.CONTENT_DISPOSITION, "attachment; filename=\"" + defaultFileName + "\"" ); // 移除提前调用的flushBuffer(),先获取输出流再写入内容 // 使用try-with-resources自动管理流资源,无需手动close try (OutputStream out = resourceResponse.getPortletOutputStream()) { out.write(backendFile.getContent()); out.flush(); } catch (IOException e) { // 最好打印完整异常栈,方便排查问题 logger.error("文件下载失败: {}", e.getMessage(), e); } return;
额外注意事项
- 绝对不要在获取输出流/写入器之前调用
flushBuffer(),这会强制Liferay提交响应,后续无法再修改响应体或切换输出方式 - 如果你的Portlet存在视图渲染逻辑,要确保下载请求是独立的资源请求(比如通过
@ResourceMapping处理),不会触发视图渲染的分支——这也是日志里出现SoyDataView的原因,可能你的请求同时触发了视图渲染和文件下载的逻辑 - 文件名尽量避免特殊字符,必须包含的话一定要用双引号包裹
内容的提问来源于stack exchange,提问作者user3927150
相关产品推荐
相关产品推荐

