一:编写拦截器
@Component @Slf4j public class ApiInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { PrintWriter out = response.getWriter(); HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); // 如果打上了AuthToken注解则需要验证token if (method.getAnnotation(AuthToken.class) != null || handlerMethod.getBeanType().getAnnotation(AuthToken.class) != null) { String uri = request.getRequestURI(); //从header中拿token,并进行解析 String token = request.getHeader("token"); if (StringUtils.isBlank(token)) { out.append(JSONObject.fromObject(PsBudgetResultVO .builder() .code(BaseConstants.STRING_ZERO) .msg("请尝试登录!") .build()) .toString()); return false; } } return true; } }
二:注册拦截器
@Configuration @EnableWebMvc @EnableWebSocket @ServletComponentScan(basePackages = "com.cn") public class WebConfig implements WebSocketConfigurer,WebMvcConfigurer { @Resource private ApiInterceptor apiInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(apiInterceptor) .addPathPatterns("/api/**"); } @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { } }
三:发送请求
发送请求:http://localhost:8080/**/api/****?
报错:
四:分析报错原因
根据报错信息:
java.lang.IllegalStateException: getWriter() has already been called for this response
at org.apache.catalina.connector.Response.getOutputStream(Response.java:550) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.connector.ResponseFacade.getOutputStream(ResponseFacade.java:210) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at javax.servlet.ServletResponseWrapper.getOutputStream(ServletResponseWrapper.java:107) ~[javax.servlet-api-4.0.1.jar:4.0.1]
at javax.servlet.ServletResponseWrapper.getOutputStream(ServletResponseWrapper.java:107) ~[javax.servlet-api-4.0.1.jar:4.0.1]
at org.springframework.security.web.util.OnCommittedResponseWrapper.getOutputStream(OnCommittedResponseWrapper.java:145) ~[spring-security-web-5.3.9.RELEASE.jar:5.3.9.RELEASE]
at javax.servlet.ServletResponseWrapper.getOutputStream(ServletResponseWrapper.java:107) ~[javax.servlet-api-4.0.1.jar:4.0.1]
at org.springframework.security.web.util.OnCommittedResponseWrapper.getOutputStream(OnCommittedResponseWrapper.java:145) ~[spring-security-web-5.3.9.RELEASE.jar:5.3.9.RELEASE]
我们可以看到Response.java:550位置处报错。
我们可以看到usingWriter=true,是导致异常的原因。
那么接下来我们只要知道哪一步将usingWriter=true就可以了。
(一):源码1(Response550)
@Override public ServletOutputStream getOutputStream() throws IOException { /** * 1:判断usingWriter是否为true,为true则直接判处异常 */ if (usingWriter) { throw new IllegalStateException (sm.getString("coyoteResponse.getOutputStream.ise")); } /** * 1:将usingOutputStream 赋值为true */ usingOutputStream = true; if (outputStream == null) { outputStream = new CoyoteOutputStream(outputBuffer); } return outputStream; }
(二):源码2
我么再在拦截器中使用了getWriter(),方法,方便我们返回我们的自定义错误信息。
源码getWriter()源码如下:
@Override public PrintWriter getWriter() throws IOException { /** *1:首先判断 usingOutputStream,当usingOutputStream为true,直接抛出异 *常 */ 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()); } /** *2:将 usingWriter = true */ usingWriter = true; outputBuffer.checkConverter(); if (writer == null) { writer = new CoyoteWriter(outputBuffer); } return writer; }
看了上面我们可以知道,源码1中使我们的http请求默认使用的,源码2 是我们用来返回自定义异常的。这两处是相互冲突的,我们只能在一个http请求中使用一种返回值说明。
五:解决
问题已经明确,下面就是如何解决了。
(一):适用我自己的情况,因为我只需要在返回false的时候才会需要自定义返回值,所以我只需要在返回false去调用PrintWriter out = response.getWriter();即可。
修改后代码如下:
@Component
@Slf4j
@Component
@Slf4j
public class ApiInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws IOException {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 如果打上了AuthToken注解则需要验证token
if (method.getAnnotation(AuthToken.class) != null || handlerMethod.getBeanType().getAnnotation(AuthToken.class) != null) {
String uri = request.getRequestURI();
//从header中拿token,并进行解析
String token = request.getHeader("token");
if (StringUtils.isBlank(token)) {
PrintWriter out = response.getWriter();
out.append(JSONObject.fromObject(ResultVO
.builder()
.code(BaseConstants.STRING_ZERO)
.msg("请尝试登录!")
.build())
.toString());
return false;
}
}
return true;
}
}
(二):我们在一个请求中只使用一种返回方式即可。
修改后如下:
@Component
@Slf4j
@Component
@Slf4j
public class ApiInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws IOException {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 如果打上了AuthToken注解则需要验证token
if (method.getAnnotation(AuthToken.class) != null || handlerMethod.getBeanType().getAnnotation(AuthToken.class) != null) {
String uri = request.getRequestURI();
//从header中拿token,并进行解析
String token = request.getHeader("token");
if (StringUtils.isBlank(token)) {
String res = JSONObject.fromObject(ResultVO
.builder()
.code(BaseConstants.STRING_ZERO)
.msg("请尝试登录!")
.build())
.toString();
response.setContentType("application/json;charset=utf-8");
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(res.getBytes());
return false;
}
}
return true;
}
}