一、概述
在SpringMVC中,除了Filter和Interceptor拦截器外,还有对请求Controller的处理,即对请求和响应内容的处理和对请求参数的处理。
二、ControllerAdvice
@ControllerAdvice本质上同Component一样,因此也会被当成组件扫描。
其中@ExceptionHandler常用到。即抛出的异常会被统一拦截处理。在项目中对MethodArgumentNotValidException异常拦截处理
@ControllerAdvicepublic class GlobalHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public Result exceptionHandler(MethodArgumentNotValidException e) { Result result = new Result(BizExceptionEnum.INVALID_REQ_PARAM.getErrorCode(), BizExceptionEnum.INVALID_REQ_PARAM.getErrorMsg()); logger.error("req params error", e); return result; }}// 上述对MethodArgumentNotValidException异常统一拦截后并统一返回异常
实现原理:
public class DispatcherServlet extends FrameworkServlet { // ...... protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); // 处理所有异常 initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); } // ......}
DispatcherServlet的initHandlerExceptionResolvers(context)方法,方法会取得所有实现了HandlerExceptionResolver接口的bean并保存起来,其中就有一个类型为ExceptionHandlerExceptionResolver的bean,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理,关键代码在这里
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean { // ...... private void initExceptionHandlerAdviceCache() { // ...... List adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); AnnotationAwareOrderComparator.sort(adviceBeans); for (ControllerAdviceBean adviceBean : adviceBeans) { ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType()); if (resolver.hasExceptionMappings()) { // 找到所有ExceptionHandler标注的方法并保存成一个ExceptionHandlerMethodResolver类型的对象缓存起来 this.exceptionHandlerAdviceCache.put(adviceBean, resolver); if (logger.isInfoEnabled()) { logger.info("Detected @ExceptionHandler methods in " + adviceBean); } } // ...... } }}public ExceptionHandlerMethodResolver(Class handlerType) { // 查询当前类中@ExceptionHandler的方法 for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { // 获取方法的异常类型 for (Class exceptionType : detectExceptionMappings(method)) { // 添加到缓存中 addExceptionMapping(exceptionType, method); } }}
最后ExceptionHandler被执行过程
// 处理返回结果时,如果异常不为空,则进行异常处理private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { boolean errorView = false; if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { // ... } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); // 调用异常Handler处理 mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // ...}
三、RequestBodyAdvice和ResponseBodyAdvice
该类是对入参或者是返回值的处理
RequestBodyAdvice和ResponseBodyAdvice
在ControllerAdvice中,还包含了RequestBodyAdvice,ResponseBodyAdvice
- RequestBodyAdvice对请求Body的处理
- ResponseBodyAdvice对响应Body的处理
3.1 Spring如何管理
RequestBodyAdvice和ResponseBodyAdvice的实现方式是RequestResponseBodyAdviceChain,其中存储了xxBodyAdvice的方法,在spring处理请求参数和返回数据是被调用。
class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice
我们知道所有的xxxBodyAdvice最终都是通过暴露的RequestResponseBodyAdviceChain来使用的,它内部持有容器内所有的Advice的引用。由于RequestResponseBodyAdviceChain的访问权限是default,所以这套机制完全由Spring内部控制。
他唯一设值处是:AbstractMessageConverterMethodArgumentResolver。
AbstractMessageConverterMethodArgumentResolver(一般实际为RequestResponseBodyMethodProcessor): // 唯一构造函数,指定所有的advices public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter> converters, @Nullable List requestResponseBodyAdvice) { Assert.notEmpty(converters, "'messageConverters' must not be empty"); this.messageConverters = converters; this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters); this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice); }
此构造函数在new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)时候调用,传进来的requestResponseBodyAdvice就刚好是在初始化RequestMappingHandlerAdapter的时候全局扫描进来的所有的增强器们
3.2 如何使用
请求日志的打印,用于POST请求的,这里实现了RequestBodyAdvice用来打印请求参数,也使用了ResponseBodyAdvice打印返回的信息
// 生成日志信息,并放到request中@ControllerAdvicepublic class RequestBodyAdviceHandler implements RequestBodyAdvice { public RequestBodyAdviceHandler() { } public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter> converterType) { Method method = methodParameter.getMethod(); Class declaringClass = method.getDeclaringClass(); RestController RestController = (RestController)declaringClass.getAnnotation(RestController.class); return RestController != null; } public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter> converterType) throws IOException { return inputMessage; } public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter> converterType) { this.writeRequestLog(body, inputMessage, parameter, targetType, converterType); return body; } public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter> converterType) { this.writeRequestLog(body, inputMessage, parameter, targetType, converterType); return body; } private String toJSONString(Object body, MethodParameter parameter) { IgnoreLogBody ignore = (IgnoreLogBody)parameter.getMethodAnnotation(IgnoreLogBody.class); if (ignore == null) { return JSON.toJSONString(body); } else { String[] ignoreKey = ignore.ignoreKey(); return ignoreKey != null && ignoreKey.length != 0 ? JSON.toJSONString(body, new IgnoreLogPropertyFilter(ignore.ignoreKey(), ignore.key()), new SerializerFeature[0]) : JSON.toJSONString(body); } } private void writeRequestLog(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter> converterType) { HttpServletRequest request = RequestHelper.getRequest(); request.setAttribute("_REQUEST_STARTTIME_", System.currentTimeMillis()); String requestId = request.getHeader("_REQUEST_ID_"); if (StringUtils.isEmptyStr(requestId)) { requestId = TraceContext.traceId(); } if (StringUtils.isEmptyStr(requestId) || "Ignored_Trace".equals(requestId)) { requestId = UUID.randomUUID().toString().replaceAll("-", ""); } request.setAttribute("_REQUEST_ID_", requestId); StringBuilder info = new StringBuilder("==>\n"); info.append("[*=请求requestID=]>: ").append(requestId).append("\n"); info.append("[==请求地址=======]>: ").append(request.getRequestURL().toString()).append("\n"); info.append("[==请求方法=======]>: ").append(request.getMethod()).append("\n"); info.append("[==操作用户=======]>: ").append(UserInfoContext.getCurrentUserCode()).append("\n"); info.append("[==客户IP========]>: ").append(RequestHelper.getClientIP()).append("\n"); info.append("[==映射方法=======]>: ").append(parameter.getMethod()).append(".").append("\n"); if (body == null) { info.append("[==请求参数=======]>: ").append("该接口未定义参数或参数为空"); } else if (converterType == FastJsonHttpMessageConverter.class) { info.append("[==请求参数=======]>: ").append(this.toJSONString(body, parameter)); } else { info.append("[==请求参数=======]>: ").append(converterType); } info.append("\n"); request.setAttribute("_REQUEST_LOG_INFO_", info); }}
@ControllerAdvicepublic class ResponseBodyAdviceHandler implements ResponseBodyAdvice { private static final IEventLogger logger = Logtube.getLogger(ResponseBodyAdviceHandler.class.getName()); public ResponseBodyAdviceHandler() { } public boolean supports(MethodParameter methodParamter, Class<? extends HttpMessageConverter> converterType) { return this.isRestController(methodParamter); } public Object beforeBodyWrite(Object body, MethodParameter methodParamter, MediaType selectedContentType, Class<? extends HttpMessageConverter> selectedConverterType, ServerHttpRequest req, ServerHttpResponse response) { HttpServletRequest request = RequestHelper.getRequest(); if (request == null) { return body; } else { StringBuilder info = (StringBuilder)request.getAttribute("_REQUEST_LOG_INFO_"); if (info == null) { info = new StringBuilder(); } String requestBodyData = null; if (body == null) { requestBodyData = null; } else if (selectedContentType.includes(MediaType.APPLICATION_JSON)) { requestBodyData = JSON.toJSONString(body); } else { requestBodyData = body.toString(); } Long startTime = (Long)request.getAttribute("_REQUEST_STARTTIME_"); info.append("[==响应结果=======]>: ").append(requestBodyData == null ? "null" : requestBodyData); info.append("\n"); if (startTime != null) { info.append("[==执行耗时=======]>: ").append(System.currentTimeMillis() - startTime).append("ms").append("\n"); } String requestId = (String)request.getAttribute("_REQUEST_ID_"); logger.info(info.toString()); return body; } } private boolean isRestController(MethodParameter methodParamter) { RestController annotation = (RestController)methodParamter.getDeclaringClass().getAnnotation(RestController.class); return annotation != null; }}
最后打印的日志信息
四、HandlerMethodReturnValueHandler4.1 HandlerMethodReturnValueHandler
对返回信息做特殊处理,且只会被调用一次,谨慎处理
默认情况下,spring会调用RequestResponseBodyMethodProcessor来处理返回执行。它实现了HandlerMethodReturnValueHandler的handleReturnValue的方法,
如何选择returnHandler是在HandlerMethodReturnValueHandlerComposite类做了选择
@Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { // 选择返回的handler处理HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());} // 执行handleReturnValue将数据写入到reponse流中handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}
// 被@ResponseBody注解的方法,会被执行该类@Overridepublic boolean supportsReturnType(MethodParameter returnType) { return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class));}//将returnValue写入到流中@Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // Try even with null return value. ResponseBodyAdvice could get involved. writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);}
知道了HandlerMethodReturnValueHandler是用于返回数据的handler,那么自己实现这个类用户封装自己返回的方法。
4.2 如何使用
一般请求下,返回的数据如下:
{ "data": { }, "errorCode": "0", "errorMsg": "成功",}
data是其真实数据。在Controller层,需要调用如下
@RestController@RequestMapping("/stock/depotinventorybalance")public class DepotInventoryBalanceController {@RequestMapping(value = "findById", method = RequestMethod.POST)public DepotInventoryBalanceDto findById(@RequestBody DepotInventoryBalanceDto depotInventoryBalance) {DepotInventoryBalanceDto balance = this.depotInventoryBalanceService.findById(depotInventoryBalance); return ResponseResultUtil.result(balance);}}@Datapublic class ResponseResult {private T data; private String errorCode; private String errorMsg; }public static class ResponseResultUtil { public static ResponseResult result(T data){ ResponseResult result = new ResponseResult(); result.setErrorCode( ErrorCode.SUCCESS.getCode()); result.setErrorMsg(ErrorCode.SUCCESS.getMessage()); result.setData(data); return result; }}
那么通过注解@AutoResult可以将ResponseResultUtil替换调用,就是在处理HandlerMethodReturnValueHandler时,处理自己handleReturnValue返回数据。但是这里有一个点,就是实现了自己的类,那么自己实现的ResponseBodyAdvice,将不会被调用,因为AutoResultReturnValueHandler拦截的请求,会直接返回,不会再调用后续的handler方法。
@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface AutoResult { boolean value() default true;}public class AutoResultReturnValueHandler implements HandlerMethodReturnValueHandler { private static final IEventLogger logger = Logtube.getLogger(AutoResultReturnValueHandler.class.getName()); public AutoResultReturnValueHandler() { } public boolean supportsReturnType(MethodParameter returnType) { return this.isRestController(returnType) && this.isAutoResult(returnType); } public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class); mavContainer.setRequestHandled(true); HttpServletResponse response = (HttpServletResponse)webRequest.getNativeResponse(HttpServletResponse.class); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); StringBuilder info = (StringBuilder)request.getAttribute("_REQUEST_LOG_INFO_"); if (info == null) { info = new StringBuilder(); } String requestId = (String)request.getAttribute("_REQUEST_ID_"); ResponseResult result = new ResponseResult(); result.setData(returnValue); result.setErrorCode( ErrorCode.SUCCESS.getCode()); result.setErrorMsg(ErrorCode.SUCCESS.getMessage()); result.setRequestId(requestId); String jsonString = JSON.toJSONString(result, new SerializerFeature[]{SerializerFeature.WriteDateUseDateFormat}); Long startTime = (Long)request.getAttribute("_REQUEST_STARTTIME_"); info.append("[==响应结果=======]>: ").append(jsonString); info.append("\n"); if (startTime != null) { info.append("[==执行耗时=======]>: ").append(System.currentTimeMillis() - startTime).append("ms").append("\n"); } logger.info(info.toString()); response.getWriter().append(jsonString); } private boolean isRestController(MethodParameter returnType) { RestController annotation = (RestController)returnType.getDeclaringClass().getAnnotation(RestController.class); return annotation != null; } private boolean isAutoResult(MethodParameter returnType) { AutoResult methodAnnotation = (AutoResult)returnType.getMethodAnnotation(AutoResult.class); if (methodAnnotation != null) { return methodAnnotation.value(); } else { AutoResult annotation = (AutoResult)returnType.getDeclaringClass().getAnnotation(AutoResult.class); return annotation != null && annotation.value(); } }}// 该类下的所有方法都会被拦截@AutoResult@RestController@RequestMapping("/stock/depotinventorybalance")public class DepotInventoryBalanceController {@RequestMapping(value = "findById", method = RequestMethod.POST)public DepotInventoryBalanceDto findById(@RequestBody DepotInventoryBalanceDto depotInventoryBalance) {return this.depotInventoryBalanceService.findById(depotInventoryBalance);} // 导出的方法,最后是已文件流的方式返回, // 不使用AutoResult返回的结果形式,即不使用自定义Handler类 @AutoResult(value = false) public void export() { depotInventoryBalanceService.export(); }}