java 拦截、过滤器2

一、概述

在SpringMVC中,除了Filter和Interceptor拦截器外,还有对请求Controller的处理,即对请求和响应内容的处理和对请求参数的处理。
image.png

二、ControllerAdvice

@ControllerAdvice本质上同Component一样,因此也会被当成组件扫描。
其中@ExceptionHandler常用到。即抛出的异常会被统一拦截处理。在项目中对MethodArgumentNotValidException异常拦截处理

@ControllerAdvice
public 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<ControllerAdviceBean> 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<? extends Throwable> 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<Object> {
  //它持有所有的,记住是所有的advice们
  private final List<Object> requestBodyAdvice = new ArrayList<>(4);
  private final List<Object> responseBodyAdvice = new ArrayList<>(4);

  // 可以看到这是个通用的方法。内来进行区分存储的   getAdviceByType这个区分方法可以看一下
  // 兼容到了ControllerAdviceBean以及beanType本身
  public RequestResponseBodyAdviceChain(@Nullable List<Object> 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 <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
    // 简单的说你想要的是Request的还是Response的List呢?
    List<Object> availableAdvice = getAdvice(adviceType);
    if (CollectionUtils.isEmpty(availableAdvice)) {
      return Collections.emptyList();
    }
    List<A> 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<Object> 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中
@ControllerAdvice
public 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);
    }
}
@ControllerAdvice
public class ResponseBodyAdviceHandler implements ResponseBodyAdvice<Object> {
    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;
    }
}

最后打印的日志信息
image.png

四、HandlerMethodReturnValueHandler

4.1 HandlerMethodReturnValueHandler

对返回信息做特殊处理,且只会被调用一次,谨慎处理
默认情况下,spring会调用RequestResponseBodyMethodProcessor来处理返回执行。它实现了HandlerMethodReturnValueHandler的handleReturnValue的方法,
如何选择returnHandler是在HandlerMethodReturnValueHandlerComposite类做了选择

	@Override
	public 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注解的方法,会被执行该类
@Override
public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
            returnType.hasMethodAnnotation(ResponseBody.class));
}
//将returnValue写入到流中
@Override
public 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);
	}
}

@Data
public class  ResponseResult<T> {
	private T data;
    private String errorCode;
    private String errorMsg; 

}

public static class ResponseResultUtil<T> {
    public static <T> ResponseResult<T> result(T data){
        ResponseResult<T> 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)
@Documented
public @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();
    }
}


posted @ 2023-07-23 15:43  冰魄秋雨  阅读(149)  评论(0编辑  收藏  举报