背景
项目中需要用到的数据校验、异常捕获,所以会用@ExceptionHandler方法,对Controller做统一的全局异常处理。当发生异常时,需要记录异常的url、入参、出参、错误信息等。但是request的inputStream已经读取过一次,此时拿到的request是空的。
解决方法
Spring提供了一个ContentCachingRequestWrapper,用于缓存从输入流读取出来的内容。我们可以在过滤器(Filter)中,将HttpServletRequest包装成ContentCachingRequestWrapper,这样后续就可以重复获取到请求体内容了。
定义RequestWrapperFilter
@Component
public class RequestWrapperFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {filterChain.doFilter(new ContentCachingRequestWrapper(httpServletRequest), httpServletResponse);}
}
修改GlobalExceptionHandler
@Slf4j
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {public static final String SERVICE_EXCEPTION = "order_exception";/*** 声明要捕获的异常*/@ExceptionHandler({BindException.class,MethodArgumentNotValidException.class,GlobalException.class,Exception.class})@ResponseBodypublic <T> BaseResponse<T> handleException(Exception e) {BaseResponse<T> baseResponse;if (e instanceof BindException) {baseResponse = handle(((BindException) e).getBindingResult());} else if (e instanceof MethodArgumentNotValidException) {baseResponse = handle(((MethodArgumentNotValidException) e).getBindingResult());} else if (e instanceof GlobalException) {HttpError httpError = ((GlobalException) e).getHttpError();baseResponse = ResponseUtils.fail(httpError.getCode(), httpError.getMessage());} else {baseResponse = ResponseUtils.fail(OrderHtapResultEnum.SYSTEM_ERROR.getCode(), "系统异常:" + e);}ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();log.error("handleException catch exception!url:{},request:{},response:{},error:{}", request.getRequestURI(), JSON.toJSONString(getRequestBody(request)), baseResponse, e);MetricsApi.getInstance().reportCounter(SERVICE_EXCEPTION, 1);return baseResponse;}//重点是这里private static String getRequestBody(HttpServletRequest request) {ContentCachingRequestWrapper requestWrapper =WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);if (requestWrapper == null) {return "";}return new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);}private <T> BaseResponse<T> handle(BindingResult bindingResult) {if (bindingResult.hasErrors()) {log.error("verifyParams error --> " + bindingResult.toString());FieldError fieldError = bindingResult.getFieldError();return ResponseUtils.fail(OrderHtapResultEnum.PARAM_ERROR.getCode(), OrderHtapResultEnum.PARAM_ERROR.getMessage() + "(" + fieldError.getField() + ":" + fieldError.getDefaultMessage() + ")");}return ResponseUtils.fail(OrderHtapResultEnum.PARAM_ERROR.getCode(), OrderHtapResultEnum.PARAM_ERROR.getMessage());}
}
这里只适合post,put调用的方法,get调用的获取不到。