通常在项目中都会使用到全局异常处理,但是如果添加有拦截器,对拦截器中的异常进行捕获的时候,就会发现全局异常处理失效,无法对拦截器的异常进行捕获。
全局异常不能捕获拦截器异常的原因
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。