一、概述

在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

  1. RequestBodyAdvice对请求Body的处理
  2. ResponseBodyAdvice对响应Body的处理

3.1 Spring如何管理

RequestBodyAdvice和ResponseBodyAdvice的实现方式是RequestResponseBodyAdviceChain,其中存储了xxBodyAdvice的方法,在spring处理请求参数和返回数据是被调用。

class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice {  //它持有所有的,记住是所有的advice们  private final List requestBodyAdvice = new ArrayList(4);  private final List responseBodyAdvice = new ArrayList(4);  // 可以看到这是个通用的方法。内来进行区分存储的   getAdviceByType这个区分方法可以看一下  // 兼容到了ControllerAdviceBean以及beanType本身  public RequestResponseBodyAdviceChain(@Nullable List requestResponseBodyAdvice) {    this.requestBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, RequestBodyAdvice.class));    this.responseBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, ResponseBodyAdvice.class));  }  @Override  public boolean supports(MethodParameter param, Type type, Class<? extends HttpMessageConverter> converterType) {    throw new UnsupportedOperationException("Not implemented");  }  @Override  public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter> converterType) {    throw new UnsupportedOperationException("Not implemented");  }  // 可以看到最终都是委托给具体的Advice去执行的(supports方法)  // 特点:符合条件的所有的`Advice`都会顺序的、依次的执行  @Override  public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter> converterType) throws IOException {    for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {      if (advice.supports(parameter, targetType, converterType)) {        request = advice.beforeBodyRead(request, parameter, targetType, converterType);      }    }    return request;  }  ... // 其余方法略。处理逻辑同上顺序执行。  // 最重要的是如下这个getMatchingAdvice()匹配方法  private  List getMatchingAdvice(MethodParameter parameter, Class adviceType) {    // 简单的说你想要的是Request的还是Response的List呢?    List availableAdvice = getAdvice(adviceType);    if (CollectionUtils.isEmpty(availableAdvice)) {      return Collections.emptyList();    }    List result = new ArrayList(availableAdvice.size());    for (Object advice : availableAdvice) {      if (advice instanceof ControllerAdviceBean) {        ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;        // 这里面会调用beanTypePredicate.test(beanType)方法        // 也就是根据basePackages等等判断此advice是否是否要作用在本类上        if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {          continue;        }        advice = adviceBean.resolveBean();      }      // 当前的advice若是满足类型要求的,那就添加进去  最终执行切面操作      if (adviceType.isAssignableFrom(advice.getClass())) {        result.add((A) advice);      }    }    return result;  }}

我们知道所有的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();    }}
Copyright © maxssl.com 版权所有 浙ICP备2022011180号