首页 > 编程语言 > 详细

spring mvc controller 方法处理参数的过程

时间:2019-08-08 16:07:08      阅读:117      评论:0      收藏:0      [点我收藏+]

今天有个需求:每个请求设置一个唯一的标识,目前是用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 是没有作用的。

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

spring mvc controller 方法处理参数的过程

原文:https://www.cnblogs.com/zhangchenglzhao/p/11321344.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!