zoukankan      html  css  js  c++  java
  • spring mvc controller 方法处理参数的过程。RequestWrapper 包装 @RequestBody 参数 。。 filter , inter,aop

    参考: https://blog.csdn.net/q957967519/article/details/91544888

    今天有个需求:每个请求设置一个唯一的标识,目前是用uuid,用于数据库主键,当然也用于打印日志的时候有个唯一标识。

    目前的代码是这样的, Qrs 有个属性uuid. 

        @ResponseBody
        @RequestMapping(value = "/trans", method = RequestMethod.POST,produces = "application/json;charset=UTF-8")
        public String trans(@RequestBody Qrs req){
          req.setUuid(xxx);
          MDC.put("uuid",xxx); //MDC 是logback的一个设置公共参数的类。 在logback.xml 配置pattern 使用 %X{uuid}即可打印唯一标识了
    }

    这样写的话,我岂不是要在每个controller 方法都要加上这一句。 那就加个filter 在进入方法前统一加上就行了

    public class MyFilter implements Filter { 

      @Override
      
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response) throws ServletException {
        request.setAttribute("uuid",xxx);  

        ....
    }
    }

    加上后,Qrs 的 uuid 仍然是null。 于是去了解下 spring mvc 去如何给参数赋值的。

    debug 跟源码总结 分两步 : 

    1. HandlerMethodArgumentsResolverComposite  遍历所有HandlerMethodArgumentResolver的实现类,调用其

      supportsParameter 方法判断该resolver 是否可以处理该参数(通常判断依据就是参数的注解,如@RequestBody)

    2. 根据找到的resolver , 调用其 resolveArgument()方法, 该方法中会调用相关的messageConverters 给参数赋值。

    具体代码: 

    HandlerMethodArgumentResolverComposite 类: 
    1. 判断是否有能处理该参数的resolver 
    public boolean supportsParameter(MethodParameter parameter) {
            return this.getArgumentResolver(parameter) != null;
        }
    
    2. 如果有 ,存入argumentResolverCache(当有相同参数类型是,直接去该resolver)
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
            HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
            if (result == null) {
                Iterator var3 = this.argumentResolvers.iterator();
    
                while(var3.hasNext()) {
                    HandlerMethodArgumentResolver methodArgumentResolver = (HandlerMethodArgumentResolver)var3.next();
                    if (this.logger.isTraceEnabled()) {
                        this.logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" + parameter.getGenericParameterType() + "]");
                    }
    
                    if (methodArgumentResolver.supportsParameter(parameter)) {
                        result = methodArgumentResolver;
                        this.argumentResolverCache.put(parameter, methodArgumentResolver);
                        break;
                    }
                }
            }
    
            return result;
        }
    
    3. 调用resolveArgument方法,实际是调用上一步找到resolver. 
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
            HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
            Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]");
            return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        }

    本例 是@RequestBody 注解,所以找到resolver 是 RequestResponseBodyMethodProcessor

    RequestResponseBodyMethodProcessor 类:
    @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.hasParameterAnnotation(RequestBody.class);//所以支持@RequestBody 注解的参数
        }
    
    2. 处理参数,主要readWithMessageConverters
    @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    
            Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
            String name = Conventions.getVariableNameForParameter(parameter);
            WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
            if (arg != null) {
                validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                    throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
                }
            }
            mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
            return arg;
        }
    3. 该类继承AbstractMessageConverterMethodArgumentResolver,重要代码是
    this.messageConverters 这个循环,找到相应转换类,跟踪代码找到的是MappingJackson2HttpMessageConverter
    @SuppressWarnings("unchecked")
        protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage,
                MethodParameter methodParam, Type targetType) throws IOException, HttpMediaTypeNotSupportedException {
    
            MediaType contentType;
            try {
                contentType = inputMessage.getHeaders().getContentType();
            }
            catch (InvalidMediaTypeException ex) {
                throw new HttpMediaTypeNotSupportedException(ex.getMessage());
            }
            if (contentType == null) {
                contentType = MediaType.APPLICATION_OCTET_STREAM;
            }
    
            Class<?> contextClass = methodParam.getContainingClass();
            Class<T> targetClass = (Class<T>)
                    ResolvableType.forMethodParameter(methodParam, targetType).resolve(Object.class);
    
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                if (converter instanceof GenericHttpMessageConverter) {
                    GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
                    if (genericConverter.canRead(targetType, contextClass, contentType)) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Reading [" + targetType + "] as "" +
                                    contentType + "" using [" + converter + "]");
                        }
                        return genericConverter.read(targetType, contextClass, inputMessage);
                    }
                }
                if (converter.canRead(targetClass, contentType)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Reading [" + targetClass.getName() + "] as "" +
                                contentType + "" using [" + converter + "]");
                    }
                    return ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
                }
            }
    
            throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
        }
    
    4. 对应 AbstractJackson2HttpMessageConverter.read 方法,读取请求流中的数据,
    后面估计是利用反射赋值,没有再深入了解了。
    private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
            try {
                return this.objectMapper.readValue(inputMessage.getBody(), javaType);
            } catch (IOException var4) {
                throw new HttpMessageNotReadableException("Could not read JSON: " + var4.getMessage(), var4);
            }
        }

    总结:controller 方法中的@RequestBody pojo 对象是从 输入流中获取数据的,所以操作request.setAttribute 是没有作用的。

    暂未找到实现文章开头需求的方法,有知道的大神请留言。

    今天又心血来潮,还是想在进入方法前处理RequestBody 对象,要加日志,加验签, 找了一下,找到一个方法:

    使用filter + RequestWrapper 对请求流包装。 

    继承HttpServletRequestWrapper ,可以读取request 的 流,然后在重写其getInputStream 把想要的数据在放回数据流

    import org.apache.commons.lang.StringUtils;
    
    import javax.servlet.ReadListener;
    import javax.servlet.ServletInputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import java.io.*;
    
    /**
     * @Author zc
     * @Date 2019/9/19
     */
    public class RequestWrapper extends HttpServletRequestWrapper {
    
        private final String body;
    
        /**
         * 处理数据: 验签 + 日志
         * @param body
         * @return
         */
        private String handlerData(String body){
            if(StringUtils.isNotEmpty(body)){
    
            }
            return body;
        }
    
    
        public RequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
           //读取inputstream数据流,怎么读自己定。
            StringBuilder stringBuilder = new StringBuilder();
            BufferedReader bufferedReader = null;
            try {
                InputStream inputStream = request.getInputStream();
                if (inputStream != null) {
                    bufferedReader = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
                    char[] charBuffer = new char[128];
                    int bytesRead;
                    while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                        stringBuilder.append(charBuffer, 0, bytesRead);
                    }
                }
            } catch (IOException ex) {
                throw ex;
            } finally {
                bufferedReader = null;
            }
            String bs = stringBuilder.toString();
            //处理读取的数据,想怎么处理怎么处理
            body = handlerData(bs);
        }
    
        @Override
        public ServletInputStream getInputStream() throws IOException {
            //记得将处理后的数据放回流中
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes("UTF-8"));
            ServletInputStream servletInputStream = new ServletInputStream() {
                public boolean isFinished() {
                    return false;
                }
                public boolean isReady() {
                    return false;
                }
                public void setReadListener(ReadListener readListener) {}
                public int read(){
                    return byteArrayInputStream.read();
                }
            };
            return servletInputStream;
    
        }
    }





    //filter 使用该包装类对request包装后,转发

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

      if(request instanceof HttpServletRequest) {
        requestWrapper = new RequestWrapper((HttpServletRequest) request);
      }
      if(requestWrapper == null) {
        chain.doFilter(request, response);
      } else {
        chain.doFilter(requestWrapper, response);
      }
    }

    但是我使用本方式有个难点:我的秘钥需要从数据库读取,项目使用spring mvc , 我想用@Autowired注入机构service. 但是注意到包装类是
    通过new RequestWrapper 方式创建实例的,而且filter 也是没法使用spring mvc 注解的。
    上述问题解决办法:1. RequestBodyAdvice 和 ResponseBodyAdvice 分别对请求响应参数处理。
    2. 直接使用aop环绕通知,方法前后织入代码
    filter 对所有请求其作用,其依赖servlet容器。 inter 只是对action 起作用,不依赖servlet.
    filter 就是一个方法回调, inter是基于java 反射的。
    filter 可以理解是在进入请求之前的一个处理,而inter 是一个围绕请求的一个处理。
  • 相关阅读:
    v-cloak 的用法
    vuejs开发流程
    java核心技术卷一
    删除数组重复项
    看懂oracle执行计划
    sheet制作返回按钮
    sql-server安装
    openpyxl 实现excel字母列号与数字列号之间的转换
    实战:第七章:微信H5支付时用户有微信分身停留5秒后未选择哪个微信分身,也未支付就被动回调到商户支付是否完成的页面...
    微信H5支付时用户有微信分身停留5秒后未选择哪个微信分身,也未支付就被动回调到商户支付是否完成的页面
  • 原文地址:https://www.cnblogs.com/zhangchenglzhao/p/11321344.html
Copyright © 2011-2022 走看看