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