项目场景:

在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){
                throw resolver.resolveException(request,response,                 
                               null,newCustomException("客户端信息非法!!!");
                return;
            }
            
            filterChain.doFilter(request, response);
    }
}

通过resolveException方法抛出的自定义异常可以被RestControllerAdvice捕获,从而满足我们的需求,最终得到的响应格式:

{
    
    "info": {
        "resultCode": "9999",
        "resultMsg": "客户端信息非法!!!"
    },
    "data": null
}

 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐