前言
在前些天,一位朋友在某个群里说@ExceptionHandler+@RestControllerAdvice
的方式没法捕获到@WebFilter
的异常,于是我搜了下十几个csdn的回答,好家伙,全nm是互相抄的,基本都是将异常信息set到request后再转发到自定义controller,这样转发你不嫌你的代码丑吗?我都看不下去了,好在我之前看过springboot的一些源码,有更好的解决方案,下面就来一起看下。
捕获更外层的异常
验证问题
前置条件,项目代码中已经有捕获指定异常的@ExceptionHandler
,完整代码见:https://gitee.com/wenjie2018/UT-APP/tree/global_error_filter/
这个代码比较大,因为我直接拿自己的可用项目测试的,你也可以自己新建一个springboot项目
首先,我们写一个demo,证明@ExceptionHandler+@RestControllerAdvice
确实捕获不到异常,将如下代码加到一个SpringBoot项目中:
package run.ut.app;
import run.ut.app.exception.BadRequestException;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter(urlPatterns = "/*", filterName = "sqlFilter")
public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
ServletResponse response = servletResponse;
String token = request.getHeader("token");
if (token == null || !token.equals("123")) {
throw new BadRequestException("非法请求!");
} else {
filterChain.doFilter(request, response);
}
}
@Override
public void destroy() {
}
}
之后我们启动项目,随便请求一个开放接口,观察控制台和返回值的内容,首先,下面是返回值的内容👇:
下面是控制台的日志:
在我的代码逻辑里面,我是定义了BadRequestException
的返回码为400,但按照目前的返回至来看,@ExceptionHandler
确实没生效。
覆写BasicErrorController捕获异常
捕获异常的方式也很简单,首先你得知道你抛出的异常去哪了,验证这个问题很简单,你只需要跟着异常debug一下就知道了:
具体的debug过程就不多说了,最后会来到下面这个地方👇:
从上面我们可以看到,这里其实就已经把异常对象保存到request对象中了,所以像csdn1、csdn2中自己把异常重新set一遍的操作是完全没必要的,侧面证明他们都是抄袭狗,根本没看过代码。
现在我们已经知道springboot会为我们自动保存异常信息到request中,那我们是不是还要像上面的sb抄袭博客一样要自定义一个controller然后把异常转发一遍呢?我既然写这篇博客,那肯定是不用的。
以前阅读过SpringBoot的异常处理逻辑的小伙伴,其实就知道,SpringBoot的BasicErrorController
中有个error
方法就是集中处理异常返回的,我们只需要覆写成如果是自定义异常就自定义处理器,如果不是就走别的处理器
即可,代码如下:
package run.ut.app.exception.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.ut.app.exception.UtException;
import run.ut.app.model.support.BaseResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
/**
* @author chenwenjie.star
* @date 2021/11/5 1:41 下午
*/
@RestController
@Slf4j
public class GlobalErrorController extends BasicErrorController {
public GlobalErrorController() {
this(new DefaultErrorAttributes(), new ErrorProperties());
}
public GlobalErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
super(errorAttributes, errorProperties);
}
public GlobalErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorProperties, errorViewResolvers);
}
@Override
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
log.error("进入error处理器");
Throwable throwable = (Throwable) request.getAttribute("javax.servlet.error.exception");
// 可以自定义一个handler取代下面的if else,这里为了demo足够简单先不管这么多
if (throwable instanceof UtException) {
UtException utException = (UtException) throwable;
Integer statusCode = utException.getStatus().value();
return new ResponseEntity(new BaseResponse<>(statusCode, utException.getMessage(), null), utException.getStatus());
} else {
return new ResponseEntity(new BaseResponse<>(500, "出现意料之外的错误", null), HttpStatus.valueOf(500));
}
}
}
将上述代码加入到项目后,我们来重新测试下接口的返回👇:
到此为止,问题解决,大功告成👏🏻👏🏻👏🏻,感兴趣的可以往下看看battle现场。
battle现场
这篇博客的起因是这样的,某一天某个群里某个人问了这样一个问题(过滤器指的是@WebFilter
):
之后他说用全局异常处理器捕获不到(指的是@ExceptionHandler+@RestControllerAdvice
的方式),这是当然的,因为@ExceptionHandler+@RestControllerAdvice
不是真正意义上的全局处理,而是只会处理进入controller
的异常,因为@WebFilter
的逻辑是在controller
之外的,所以会失效:
上面聊天里另一个人说的用切面也是对的(之前我写ut就是切面+注解额),但现在提问者是在维护老项目遇到了问题,换成切面的话万一漏了point那可就麻烦大了,所以最好的解决办法还是不改变原有的业务逻辑就能捕获异常。
后来我顺利拿到了demo:
再后来他查了下csdn,给出的方案是在过滤器里用forward把异常再转发给controller
:
,我觉得这种方式蠢爆了。
他看了网上的博客认为controller外的逻辑springboot都没法控制到;我认为springboot默认用的一样是servlet,肯定有收敛的地方,肯定能全局捕获。
经过了一番代码改造(就如这篇博客前面的部分),到最后风向果然改变了👇: