java 拦截、过滤器2

宁睿  金牌会员 | 2023-7-23 16:02:18 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 687|帖子 687|积分 2071

一、概述

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

二、ControllerAdvice

@ControllerAdvice本质上同Component一样,因此也会被当成组件扫描。
其中@ExceptionHandler常用到。即抛出的异常会被统一拦截处理。在项目中对MethodArgumentNotValidException异常拦截处理
  1. @ControllerAdvice
  2. public class GlobalHandler {
  3.     @ExceptionHandler(MethodArgumentNotValidException.class)
  4.     public Result exceptionHandler(MethodArgumentNotValidException e) {
  5.         Result result = new Result(BizExceptionEnum.INVALID_REQ_PARAM.getErrorCode(),
  6.                 BizExceptionEnum.INVALID_REQ_PARAM.getErrorMsg());
  7.         logger.error("req params error", e);
  8.         return result;
  9.     }
  10. }
  11. // 上述对MethodArgumentNotValidException异常统一拦截后并统一返回异常
复制代码
实现原理:
  1. public class DispatcherServlet extends FrameworkServlet {
  2.     // ......
  3.     protected void initStrategies(ApplicationContext context) {
  4.         initMultipartResolver(context);
  5.         initLocaleResolver(context);
  6.         initThemeResolver(context);
  7.         initHandlerMappings(context);
  8.         initHandlerAdapters(context);
  9.         // 处理所有异常
  10.         initHandlerExceptionResolvers(context);
  11.         initRequestToViewNameTranslator(context);
  12.         initViewResolvers(context);
  13.         initFlashMapManager(context);
  14.     }
  15.     // ......
  16. }
复制代码
DispatcherServlet的initHandlerExceptionResolvers(context)方法,方法会取得所有实现了HandlerExceptionResolver接口的bean并保存起来,其中就有一个类型为ExceptionHandlerExceptionResolver的bean,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理,关键代码在这里
  1. public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
  2.         implements ApplicationContextAware, InitializingBean {
  3.     // ......
  4.     private void initExceptionHandlerAdviceCache() {
  5.         // ......
  6.         List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
  7.         AnnotationAwareOrderComparator.sort(adviceBeans);
  8.         for (ControllerAdviceBean adviceBean : adviceBeans) {
  9.             ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
  10.             if (resolver.hasExceptionMappings()) {
  11.                 // 找到所有ExceptionHandler标注的方法并保存成一个ExceptionHandlerMethodResolver类型的对象缓存起来
  12.                 this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
  13.                 if (logger.isInfoEnabled()) {
  14.                     logger.info("Detected @ExceptionHandler methods in " + adviceBean);
  15.                 }
  16.             }
  17.             // ......
  18.         }
  19.     }
  20. }
  21. public ExceptionHandlerMethodResolver(Class<?> handlerType) {
  22.     // 查询当前类中@ExceptionHandler的方法
  23.     for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
  24.         // 获取方法的异常类型
  25.         for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
  26.             // 添加到缓存中
  27.             addExceptionMapping(exceptionType, method);
  28.         }
  29.     }
  30. }
复制代码
此构造函数在new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)时候调用,传进来的requestResponseBodyAdvice就刚好是在初始化RequestMappingHandlerAdapter的时候全局扫描进来的所有的增强器们
3.2 如何使用

请求日志的打印,用于POST请求的,这里实现了RequestBodyAdvice用来打印请求参数,也使用了ResponseBodyAdvice打印返回的信息
  1. // 处理返回结果时,如果异常不为空,则进行异常处理
  2. private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
  3.                                    @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
  4.                                    @Nullable Exception exception) throws Exception {
  5.     boolean errorView = false;
  6.     if (exception != null) {
  7.         if (exception instanceof ModelAndViewDefiningException) {
  8.             // ...
  9.         }
  10.         else {
  11.             Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
  12.             // 调用异常Handler处理
  13.             mv = processHandlerException(request, response, handler, exception);
  14.             errorView = (mv != null);
  15.         }
  16.     }
  17.     // ...
  18. }
复制代码
  1. class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice<Object> {
  2.   //它持有所有的,记住是所有的advice们
  3.   private final List<Object> requestBodyAdvice = new ArrayList<>(4);
  4.   private final List<Object> responseBodyAdvice = new ArrayList<>(4);
  5.   // 可以看到这是个通用的方法。内来进行区分存储的   getAdviceByType这个区分方法可以看一下
  6.   // 兼容到了ControllerAdviceBean以及beanType本身
  7.   public RequestResponseBodyAdviceChain(@Nullable List<Object> requestResponseBodyAdvice) {
  8.     this.requestBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, RequestBodyAdvice.class));
  9.     this.responseBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, ResponseBodyAdvice.class));
  10.   }
  11.   @Override
  12.   public boolean supports(MethodParameter param, Type type, Class<? extends HttpMessageConverter<?>> converterType) {
  13.     throw new UnsupportedOperationException("Not implemented");
  14.   }
  15.   @Override
  16.   public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
  17.     throw new UnsupportedOperationException("Not implemented");
  18.   }
  19.   // 可以看到最终都是委托给具体的Advice去执行的(supports方法)
  20.   // 特点:符合条件的所有的`Advice`都会顺序的、依次的执行
  21.   @Override
  22.   public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
  23.     for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
  24.       if (advice.supports(parameter, targetType, converterType)) {
  25.         request = advice.beforeBodyRead(request, parameter, targetType, converterType);
  26.       }
  27.     }
  28.     return request;
  29.   }
  30.   ... // 其余方法略。处理逻辑同上顺序执行。
  31.   // 最重要的是如下这个getMatchingAdvice()匹配方法
  32.   private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
  33.     // 简单的说你想要的是Request的还是Response的List呢?
  34.     List<Object> availableAdvice = getAdvice(adviceType);
  35.     if (CollectionUtils.isEmpty(availableAdvice)) {
  36.       return Collections.emptyList();
  37.     }
  38.     List<A> result = new ArrayList<>(availableAdvice.size());
  39.     for (Object advice : availableAdvice) {
  40.       if (advice instanceof ControllerAdviceBean) {
  41.         ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
  42.         // 这里面会调用beanTypePredicate.test(beanType)方法
  43.         // 也就是根据basePackages等等判断此advice是否是否要作用在本类上
  44.         if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
  45.           continue;
  46.         }
  47.         advice = adviceBean.resolveBean();
  48.       }
  49.       // 当前的advice若是满足类型要求的,那就添加进去  最终执行切面操作
  50.       if (adviceType.isAssignableFrom(advice.getClass())) {
  51.         result.add((A) advice);
  52.       }
  53.     }
  54.     return result;
  55.   }
  56. }
复制代码
最后打印的日志信息

四、HandlerMethodReturnValueHandler

4.1 HandlerMethodReturnValueHandler

对返回信息做特殊处理,且只会被调用一次,谨慎处理
默认情况下,spring会调用RequestResponseBodyMethodProcessor来处理返回执行。它实现了HandlerMethodReturnValueHandler的handleReturnValue的方法,
如何选择returnHandler是在HandlerMethodReturnValueHandlerComposite类做了选择
  1. AbstractMessageConverterMethodArgumentResolver(一般实际为RequestResponseBodyMethodProcessor):
  2.   // 唯一构造函数,指定所有的advices
  3.   public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters, @Nullable List<Object> requestResponseBodyAdvice) {
  4.     Assert.notEmpty(converters, "'messageConverters' must not be empty");
  5.     this.messageConverters = converters;
  6.     this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
  7.     this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
  8.   }
复制代码
  1. // 生成日志信息,并放到request中
  2. @ControllerAdvice
  3. public class RequestBodyAdviceHandler implements RequestBodyAdvice {
  4.     public RequestBodyAdviceHandler() {
  5.     }
  6.     public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
  7.         Method method = methodParameter.getMethod();
  8.         Class<?> declaringClass = method.getDeclaringClass();
  9.         RestController RestController = (RestController)declaringClass.getAnnotation(RestController.class);
  10.         return RestController != null;
  11.     }
  12.     public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
  13.         return inputMessage;
  14.     }
  15.     public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
  16.         this.writeRequestLog(body, inputMessage, parameter, targetType, converterType);
  17.         return body;
  18.     }
  19.     public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
  20.         this.writeRequestLog(body, inputMessage, parameter, targetType, converterType);
  21.         return body;
  22.     }
  23.     private String toJSONString(Object body, MethodParameter parameter) {
  24.         IgnoreLogBody ignore = (IgnoreLogBody)parameter.getMethodAnnotation(IgnoreLogBody.class);
  25.         if (ignore == null) {
  26.             return JSON.toJSONString(body);
  27.         } else {
  28.             String[] ignoreKey = ignore.ignoreKey();
  29.             return ignoreKey != null && ignoreKey.length != 0 ? JSON.toJSONString(body, new IgnoreLogPropertyFilter(ignore.ignoreKey(), ignore.key()), new SerializerFeature[0]) : JSON.toJSONString(body);
  30.         }
  31.     }
  32.     private void writeRequestLog(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
  33.         HttpServletRequest request = RequestHelper.getRequest();
  34.         request.setAttribute("_REQUEST_STARTTIME_", System.currentTimeMillis());
  35.         String requestId = request.getHeader("_REQUEST_ID_");
  36.         if (StringUtils.isEmptyStr(requestId)) {
  37.             requestId = TraceContext.traceId();
  38.         }
  39.         if (StringUtils.isEmptyStr(requestId) || "Ignored_Trace".equals(requestId)) {
  40.             requestId = UUID.randomUUID().toString().replaceAll("-", "");
  41.         }
  42.         request.setAttribute("_REQUEST_ID_", requestId);
  43.         StringBuilder info = new StringBuilder("==>\n");
  44.         info.append("[*=请求requestID=]>: ").append(requestId).append("\n");
  45.         info.append("[==请求地址=======]>: ").append(request.getRequestURL().toString()).append("\n");
  46.         info.append("[==请求方法=======]>: ").append(request.getMethod()).append("\n");
  47.         info.append("[==操作用户=======]>: ").append(UserInfoContext.getCurrentUserCode()).append("\n");
  48.         info.append("[==客户IP========]>: ").append(RequestHelper.getClientIP()).append("\n");
  49.         info.append("[==映射方法=======]>: ").append(parameter.getMethod()).append(".").append("\n");
  50.         if (body == null) {
  51.             info.append("[==请求参数=======]>: ").append("该接口未定义参数或参数为空");
  52.         } else if (converterType == FastJsonHttpMessageConverter.class) {
  53.             info.append("[==请求参数=======]>: ").append(this.toJSONString(body, parameter));
  54.         } else {
  55.             info.append("[==请求参数=======]>: ").append(converterType);
  56.         }
  57.         info.append("\n");
  58.         request.setAttribute("_REQUEST_LOG_INFO_", info);
  59.     }
  60. }
复制代码
知道了HandlerMethodReturnValueHandler是用于返回数据的handler,那么自己实现这个类用户封装自己返回的方法。
4.2 如何使用

一般请求下,返回的数据如下:
  1. @ControllerAdvice
  2. public class ResponseBodyAdviceHandler implements ResponseBodyAdvice<Object> {
  3.     private static final IEventLogger logger = Logtube.getLogger(ResponseBodyAdviceHandler.class.getName());
  4.     public ResponseBodyAdviceHandler() {
  5.     }
  6.     public boolean supports(MethodParameter methodParamter, Class<? extends HttpMessageConverter<?>> converterType) {
  7.         return this.isRestController(methodParamter);
  8.     }
  9.     public Object beforeBodyWrite(Object body, MethodParameter methodParamter, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest req, ServerHttpResponse response) {
  10.         HttpServletRequest request = RequestHelper.getRequest();
  11.         if (request == null) {
  12.             return body;
  13.         } else {
  14.             StringBuilder info = (StringBuilder)request.getAttribute("_REQUEST_LOG_INFO_");
  15.             if (info == null) {
  16.                 info = new StringBuilder();
  17.             }
  18.             String requestBodyData = null;
  19.             if (body == null) {
  20.                 requestBodyData = null;
  21.             } else if (selectedContentType.includes(MediaType.APPLICATION_JSON)) {
  22.                 requestBodyData = JSON.toJSONString(body);
  23.             } else {
  24.                 requestBodyData = body.toString();
  25.             }
  26.             Long startTime = (Long)request.getAttribute("_REQUEST_STARTTIME_");
  27.             info.append("[==响应结果=======]>: ").append(requestBodyData == null ? "null" : requestBodyData);
  28.             info.append("\n");
  29.             if (startTime != null) {
  30.                 info.append("[==执行耗时=======]>: ").append(System.currentTimeMillis() - startTime).append("ms").append("\n");
  31.             }
  32.             String requestId = (String)request.getAttribute("_REQUEST_ID_");
  33.             logger.info(info.toString());
  34.             return body;
  35.         }
  36.     }
  37.     private boolean isRestController(MethodParameter methodParamter) {
  38.         RestController annotation = (RestController)methodParamter.getDeclaringClass().getAnnotation(RestController.class);
  39.         return annotation != null;
  40.     }
  41. }
复制代码
data是其真实数据。在Controller层,需要调用如下
  1.         @Override
  2.         public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
  3.                         ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
  4.             // 选择返回的handler处理
  5.                 HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
  6.                 if (handler == null) {
  7.                         throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
  8.                 }
  9.         // 执行handleReturnValue将数据写入到reponse流中
  10.                 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
  11.         }
复制代码
那么通过注解@AutoResult可以将ResponseResultUtil替换调用,就是在处理HandlerMethodReturnValueHandler时,处理自己handleReturnValue返回数据。但是这里有一个点,就是实现了自己的类,那么自己实现的ResponseBodyAdvice,将不会被调用,因为AutoResultReturnValueHandler拦截的请求,会直接返回,不会再调用后续的handler方法。
  1. // 被@ResponseBody注解的方法,会被执行该类
  2. @Override
  3. public boolean supportsReturnType(MethodParameter returnType) {
  4.     return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
  5.             returnType.hasMethodAnnotation(ResponseBody.class));
  6. }
  7. //将returnValue写入到流中
  8. @Override
  9. public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
  10.         ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
  11.         throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
  12.     mavContainer.setRequestHandled(true);
  13.     ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
  14.     ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
  15.     // Try even with null return value. ResponseBodyAdvice could get involved.
  16.     writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
  17. }
复制代码



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宁睿

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表