getWriter() has already been called for this response

错误描述
在 Spring Boot 应用中使用 HttpServletResponse 输出自定义内容时报错,错误如下:
Caused by: java.lang.IllegalStateException: getWriter() has already been called for this response
at org.apache.catalina.connector.Response.getOutputStream(Response.java:547) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.apache.catalina.connector.ResponseFacade.getOutputStream(ResponseFacade.java:210) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at javax.servlet.ServletResponseWrapper.getOutputStream(ServletResponseWrapper.java:105) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.springframework.http.server.ServletServerHttpResponse.getBody(ServletServerHttpResponse.java:84) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:255) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:104) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:295) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:226) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:124) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
… 25 common frames omitted
我的代码:
@Override
public void handle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AccessDeniedException e) {
JsonResult result = JsonResult.error(e.getMessage());
httpServletResponse.setHeader(“Content-type”, “application/json;charset=UTF-8”);
httpServletResponse.setCharacterEncoding(“UTF-8”);
try (PrintWriter printWriter = httpServletResponse.getWriter()) {
printWriter.write(JSONUtil.toJsonStr(result));
} catch (IOException ex) {
log.error(“error:” + ex.getMessage());
}
}
错误分析
通过输出的错误中发现,异常来自 tomcat-embed-core-9.0.29.jar 下的 org.apache.catalina.connector.Response.getOutputStream() 方法,我们直接查看 org.apache.catalina.connector.Response.getOutputStream() 的源码:
@Override
public ServletOutputStream getOutputStream()
throws IOException {

 if (usingWriter) {
     throw new IllegalStateException
         (sm.getString("coyoteResponse.getOutputStream.ise"));
 }

 usingOutputStream = true;
 if (outputStream == null) {
     outputStream = new CoyoteOutputStream(outputBuffer);
 }
 return outputStream;

}
同时也发现了 org.apache.catalina.connector.Response.getWriter() 方法的定义:
@Override
public PrintWriter getWriter()
throws IOException {

 if (usingOutputStream) {
     throw new IllegalStateException
         (sm.getString("coyoteResponse.getWriter.ise"));
 }

 if (ENFORCE_ENCODING_IN_GET_WRITER) {
     /*
         * If the response's character encoding has not been specified as
         * described in <code>getCharacterEncoding</code> (i.e., the method
         * just returns the default value <code>ISO-8859-1</code>),
         * <code>getWriter</code> updates it to <code>ISO-8859-1</code>
         * (with the effect that a subsequent call to getContentType() will
         * include a charset=ISO-8859-1 component which will also be
         * reflected in the Content-Type response header, thereby satisfying
         * the Servlet spec requirement that containers must communicate the
         * character encoding used for the servlet response's writer to the
         * client).
         */
        setCharacterEncoding(getCharacterEncoding());
    }

    usingWriter = true;
    outputBuffer.checkConverter();
    if (writer == null) {
        writer = new CoyoteWriter(outputBuffer);
    }
    return writer;
}

通过源码中分析,在 getOutputStream() 执行过程,会将 usingOutputStream 设为 true,并判断 usingWriter 是否为 true,是则抛异常。同理,在 getWriter() 执行过程,会将 usingWriter 设为 true,并判断 usingOutputStream 是否为 true,是则抛异常。这种写法无疑是表明要么使用 getOutputStream() ,要么使用 getWriter(),两者只能使用一个,不能同时使用。
结合我遇到的错误来看,我使用了 getWriter() 来输出内容,而 Spring Boot 自带的 Tomcat 默认使用了 getOutputStream() 来处理,使用 response 的输出流不一致导致抛异常。
如此看来,我们只要统一使用 getOutputStream() 来输出就可避免此问题。
修改代码:
@Override
public void handle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AccessDeniedException e) {
JsonResult result = JsonResult.error(e.getMessage());
httpServletResponse.setHeader(“Content-type”, “application/json;charset=UTF-8”);
httpServletResponse.setCharacterEncoding(“UTF-8”);
try (ServletOutputStream outputStream = httpServletResponse.getOutputStream()) {
outputStream.print(JSONUtil.toJsonStr(result));
} catch (IOException ex) {
log.error(“error:” + ex.getMessage());
}
}
重新启动项目,测试后发现已没有报错。至此,问题解决。
解决方案
使用 getOutputStream() 替换 getWriter() 来获取输出流。
转自作者:蓝伟洪
https://blog.lanweihong.com/posts/65061/

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值