文章整理来源:Spring编程常见错误50例_spring_spring编程_bean_AOP_SpringCloud_SpringWeb_测试_事务_Data-极客时间
案例35:不能用 ControllerAdvice 处理过滤器中的异常
用 ControllerAdvice 试图捕获过滤器中的异常
@WebFilter
@Component
public class PermissionFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader("token");
if (!"111111".equals(token)) {
System.out.println("throw NotAllowException");
throw new NotAllowException();
}
chain.doFilter(request, response);
}
}
-------------------------------------------------
@RestControllerAdvice
public class NotAllowExceptionHandler {
@ExceptionHandler(NotAllowException.class)
@ResponseBody
public String handle() {
System.out.println("403");
return "{\"resultCode\": 403}";
}
}
解析:过滤器执行流程图如下
Spring 在 doDispatch() 执行用户请求时,当在请求对应的 handler 过程中发生异常,就会把异常赋值给 dispatchException,再交给 processDispatchResult() 进行处理。在 processDispatchResult() 中会继续交给 processHandlerException() 方法,选择合适的 HandlerExceptionResolver 进行处理。这里的 handlerExceptionResolvers 包含前面声明的 NotAllowExceptionHandler 的异常处理器的 ExceptionHandlerExceptionResolver 包装类。
而在 doDispatch() 会先经过 doFilter ,在 Filter 中抛出的异常并不会被 NotAllowExceptionHandler 所处理。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//省略非关键代码
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//省略非关键代码
//查找当前请求对应的 handler,并执行
//省略非关键代码
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
//省略非关键代码
解决: 在 Filter 注入 HandlerExceptionResolver ,并调用 resolveException() 方法让其自行选择 NotAllowExceptionHandler 进行处理
@WebFilter
@Component
public class PermissionFilter implements Filter {
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String token = httpServletRequest.getHeader("token");
if (!"111111".equals(token)) {
System.out.println("throw NotAllowException");
// HandlerExceptionResolver 的 resolveException() 方法进行处理
resolver.resolveException(httpServletRequest, httpServletResponse, null, new NotAllowException());
return;
}
chain.doFilter(request, response);
}
}
案例36:默认 mappedHandler == null 判断条件不成立
欲定义一个 ExceptionHandlerController 捕获 404 异常
@RestControllerAdvice
public class MyExceptionHandler {
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(Exception.class)
@ResponseBody
public String handle404() {
System.out.println("404");
return "{\"resultCode\": 404}";
}
}
解析:在 doDispatch 中先会挑选合适的 mappedHandler ,若没有 则调用 noHandlerFound() 方法。在 noHandlerFound() 方法需要将 throwExceptionIfNoHandlerFound 默认设置为 true 即可,这样就会抛出 NoHandlerFoundException 异常,从而被 doDispatch() 内的 catch 俘获。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//省略非关键代码
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//省略非关键代码
}
--------------------------------------------
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (this.throwExceptionIfNoHandlerFound) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
而即使将 throwExceptionIfNoHandlerFound 默认设置为 true, getHandler() 也一定会获取到一个 Handler 来处理当前请求,因为在 getHandler() 中有一个 SimpleUrlHandlerMapping 可以拦截所有路径的请求。mappedHandler == null 判断条件永远不会成立,显然就不可能走到 noHandlerFound(),那么就不会抛出 NoHandlerFoundException 异常,也无法被后续的异常处理器进一步处理
解决:添加配置,并修改 MyExceptionHandler 的 @ExceptionHandler 为 NoHandlerFoundException 即可
spring.resources.add-mappings=false
spring.mvc.throwExceptionIfNoHandlerFound=true
@ExceptionHandler(NoHandlerFoundException.class)