springboot 处理 filter 中抛出的异常

1年前 (2023) 程序员胖胖胖虎阿
190 0 0

前言

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%已完成,springboot 处理 filter 中抛出的异常
就在我幻想着今天接下来的时间该怎么划水的时候,现实狠狠滴给了我一巴掌!springboot 处理 filter 中抛出的异常
这时,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());
    }
	
	// 针对其他异常的处理
}

嗯,不说了,我的几个女朋友还在家等着我呢!!!
springboot 处理 filter 中抛出的异常
好了,今天的问题就分享到这里了,希望对你有帮助,开溜。

版权声明:程序员胖胖胖虎阿 发表于 2023年3月21日 上午2:56。
转载请注明:springboot 处理 filter 中抛出的异常 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...