项目场景:
在SpringBoot项目中引入Spring-Cloud-Oauth2 实现客户端权限管理,项目中使用的是客户端模式,由于框架自带的校验及异常信息给的并不友好,不符合我们自己项目异常格式。需要我们自己在拦截器
当中对客户端信息进行校验,校验成功时放行,失败时抛出自定义异常
filter逻辑如下:
@Component
public class CheckClientInfoFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException {
//客户端信息校验..
boolean isSuccess = checkClientInfo(request);
if(!isSuccess){
throw new CustomException("客户端信息非法!!!")
}
filterChain.doFilter(request, response);
}
}
全局异常处理:
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理自定义的业务异常
*
* @param req
* @param e
* @return ResponseResult
*/
@ExceptionHandler(value = BusinessException.class)
public ResponseResult<Object> customExceptionHandler(HttpServletRequest req, CustomException e) {
log.error("发生业务异常!原因是:{}", e.getErrorMsg());
//按统一响应格式返回错误信息
return ResponseResult.fail(e.getErrorCode(), e.getMessage());
}
}
问题描述
一开始以为,上面这种直接在filter抛出的异常能够被全局异常处理类捕获,但实际上的错误响应却是这样
{
"timestamp": "2021-05-13T08:53:18.355+0000",
"status": 500,
"error": "Internal Server Error",
"message": "客户端信息非法!!!",
"path": "/oauth/token"
}
并不是我们期望的响应格式,我们的自定义异常并没有被全局异常处理类(GlobalExceptionHandler)捕获,而是被Spring框架捕获了
原因分析:
在Spring Boot由于全局异常处理@RestControllerAdvice只会去捕获所有Controller层抛出的异常,所以在filter当中抛出的异常GlobalExceptionHandler类是没有感知的,
所以在filter当中抛出的异常最终会被Spring框架自带的全局异常处理类BasicErrorController捕获,返回上面我们看到的Json响应
解决方案:
方法一:
继承上面所说的BasicErrorController类,并重写error()方法,代码如下:
@Component
public class FilterErrorController extends BasicErrorController {
public FilterErrorController() {
super(new DefaultErrorAttributes(), new ErrorProperties());
}
@Override
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
//重写body自定义反参格式
Map<String,Object> myBody = new HashMap<>();
return new ResponseEntity<>(myBody, status);
}
}
这种方法虽然可以满足我们的要求,但是值得注意的是,如果重写该方法那么所有未被@RestControllerAdvice捕获的异常都会进入该方法当中,如果在filter当中抛出的异常信息不止一种,这个时候就会比较麻烦,需要通过body中携带的异常信息做判断才能做出相应的处理,但是body中携带的异常信息包括如下几个字段:
由于所有未被全局异常捕获的自定义异常,框架都会返回http状态码为500,我们只能拿到自定义异常的message而拿不到错误码,所以可用的字段只有message一项,我们要通过错误信息的字符串比对才能做出相应处理,这样的处理方式虽然也可以实现,但是非常不灵活。
方法二:
在filter当中引入HandlerExceptionResolver类,通过该类的resolveException方法抛出自定义异常,代码如下:
@Component
public class CheckClientInfoFilter extends OncePerRequestFilter {
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException {
//客户端信息校验..
boolean isSuccess = checkClientInfo(request);
if(!isSuccess){
resolver.resolveException(request,response,
null,newCustomException("客户端信息非法!!!");
return;
}
filterChain.doFilter(request, response);
}
}
通过resolveException方法抛出的自定义异常可以被RestControllerAdvice捕获,从而满足我们的需求,最终得到的响应格式:
{
"info": {
"resultCode": "400",
"resultMsg": "客户端信息非法!!!"
},
"data": null
}
评论区