前言
springboot web项目开发中,全局异常处理是一个必不可少的组件,而且springboot 本身已经对此提供了很好的支持,我们只需要一个 @RestControllerAdvice
配合 一个 @ExceptionHandler
就可以很好的实现全局异常的拦截处理了。
经过
今天接到一个需求,需要对用户进行过滤,满足要求的才放行。听到这个需求,第一反应是这不是一个过滤器就搞定的事嘛,so easy! 十分钟不到,代码就出来了。
@Component
public class UserTokenFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
if (tokenCheck()) {
throw new UserException("用户信息获取异常");
}
filterChain.doFilter(request, response);
}
}
主逻辑就是这样,校验用户的 token ,满足条件的放行,不满足条件的直接抛出异常(因为原来的代码里已经有了针对 UserException
的全局异常处理)。
@Bean
public FilterRegistrationBean registrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
UserTokenFilter filter = new UserTokenFilter();
registrationBean.setFilter(filter);
registrationBean.setName("userTokenFilter");
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
注册过滤器,这不就搞定了嘛,难道今天又是划水摸鱼的一天?
上代码,开测!
正常请求,完美通过,50%已完成,
就在我幻想着今天接下来的时间该怎么划水的时候,现实狠狠滴给了我一巴掌!
这时,postman 返回来一个
{
"timestamp": "2022-05-05T03:01:47.438+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/user/list"
}
呃…这怎么和我设想的不一样呀,我不是有全局异常处理器么?看后台日志确实是过滤器抛出了异常,也是我们自定义的UserException
,这一切都和我预想的一样,可是为什么全局异常处理器没有生效!!!
分析
其实冷静下来想一想,一个请求过来,首先要经过拦截器(如果有),过滤器(如果有),只有前面都放行了之后,请求才会真正到达处理它的 controller,而上面的例子,我们在 过滤器中抛出了异常,抛出异常以后程序不会继续向下执行,也就是请求并没有到达 controller 层。而 全局异常拦截的注解 @ControllerAdvice
从字样上就可以看出,它带有 controller
,猜想是不是它只针对 controller 层出现的异常才会处理。于是翻到源码,看到这样一句话 @ControllerAdvice By default, the methods in an @ControllerAdvice apply globally to all controllers. Use selectors such as annotations, basePackageClasses, and basePackages (or its alias value) to define a more narrow subset of targeted controllers.
。回头再想想也正常,请求在过滤器就抛出了异常,没有到达 controller 层,controller 压根不知道这件事,因此没有处理,这不也是正常的么?大概原因我们知道了,那么有没有什么方法可以解决呢?
当然你可以直接在过滤器抛出异常的地方写回错误的结果,但是既然有了全局异常处理器,我们有没有什么办法可以利用它对过滤器抛出的异常也统一处理呢?
解决方法
带着这个想法,我再往上找了找解决方法,没想到很容易就找到了。解决的思路也很见到,既然 controllerAdvice 是拦截 controller 层的异常,那么有没有什么方法可以把这个异常传递到 controller 层呢?
方法是有的,我们可以在定义一个过滤器,起作用就是专门捕获我们过滤器抛出的异常,如果捕获到异常,那么我们就把这个异常传递到指定的 controller 来处理,并把异常也传递过去,而这个controller 只做一件事,就是有请求的时候拿到传递过来的异常,然后再把异常抛出去,这样全局异常处理器不就可以感知到这个异常了嘛?上代码
定义过滤器:
public class ExceptionFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
try {
filterChain.doFilter(request, response);
} catch (UserException e) {
e.printStackTrace();
// 传递异常信息
request.setAttribute("filterError", e);
// 指定处理该请求的处理器
request.getRequestDispatcher(CommonConstant.ERROR_CONTROLLER_PATH).forward(request, response);
}
}
}
定义异常处理controller:
@RestController
public class ExceptionController {
@RequestMapping(CommonConstant.ERROR_CONTROLLER_PATH)
public void handleException(HttpServletRequest request){
throw (UserException) request.getAttribute("filterError");
}
}
public class CommonConstant {
/**
* 异常处理 controller request url
*/
public static final String ERROR_CONTROLLER_PATH = "/error/throw";
}
注册过滤器:
@Bean
public FilterRegistrationBean exceptionFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
ExceptionFilter filter = new ExceptionFilter();
registrationBean.setFilter(filter);
registrationBean.setName("exceptionFilter");
registrationBean.setOrder(-1);
return registrationBean;
}
开测,很幸运的我们拿到了预想的结果
{
"code": 500,
"msg": "用户信息获取异常",
"data": null
}
附上全局异常处理器的相关代码
@RestControllerAdvice
public class CustomExceptionHandler {
@ExceptionHandler(UserException.class)
public ResultCode HandleException(UserException e) {
return ResultCode.fail(e.getCode(), e.getMsg());
}
// 针对其他异常的处理
}
嗯,不说了,我的几个女朋友还在家等着我呢!!!
好了,今天的问题就分享到这里了,希望对你有帮助,开溜。