通常在项目中都会使用到全局异常处理,但是如果添加有拦截器,对拦截器中的异常进行捕获的时候,就会发现全局异常处理失效,无法对拦截器的异常进行捕获。
全局异常不能捕获拦截器异常的原因
SpringBoot下全局异常处理的几种方式:
- BasicExceptionController——SpringBoot默认处理异常方式,用于异常跳转到/error,可实现自定义错误页面请求。
- @ExceptionHandle注解——只能在控制器中定义异常处理方法。
- @ControllerAdvice+@ExceptionHandler——增强控制前Controller实现异常拦截。
- SimpleMappingExceptionResolver——拦截异常跳转到error页面。
- HandlerExceptionResolver——实现HandlerExceptionResolver拦截异常。
上面几种方式只能拦截到控制层的异常,而Filter在Controller之前,Controller层的异常捕获,是无法捕获到还没有请求到Controller时发生的异常的。
实现对Filter异常的捕获
从上面的几种异常处理方式可以发现,如果要捕获Filter异常,只能通过控制器层定义的全局异常处理来捕获;那么也就只能想办法让Filter中的异常发送到Controller,再由Controller抛出异常,最后由全局异常捕获。
有了上面的思路,第一个要解决的问题就是怎么让过滤器中的异常在Filter中被捕获到再发送出去。
Filter的实现方式是责任链,第一个Filter处理之后,调用第二个Filter,依次往后,直到Filter全部处理完成后结束;当某一个Filter处理中断,则依次返回结果经过前一个Filter,直到经过第一个Filter过滤后结束过滤责任链。
根据Filter过滤的特点,只需要在业务过滤器之前加上用于处理其他过滤器异常捕获的Filter就可以实现对过滤器异常的处理。
1 简单实现异常过滤Filter代码如下:
@Slf4j
@Component
public class ExceptionFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (Exception e) {
// 异常捕获,发送到error controller
request.setAttribute("filter.error", e);
//将异常分发到/error/exthrow控制器
request.getRequestDispatcher("/error/exthrow").forward(request, response);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
2 注册过滤器时,ExceptionFilter排序要再其他过滤器之前
@Bean
public FilterRegistrationBean exceptionFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(exceptionFilter);
registration.setName("exceptionFilter");
//此处尽量小,要比其他Filter靠前
registration.setOrder(-1);
return registration;
}
3 实现Controller接收过过滤器发来的异常
@RestController
public class ErrorController {
/**
* 重新抛出异常
*/
@RequestMapping("/error/exthrow")
public void rethrow(HttpServletRequest request) {
throw ((Exception) request.getAttribute("filter.error"));
}
}
以上只是Demo示例,在实际使用中,拦截Filter尽量拦截具体的Exception。