今天有个需求: 每个请求设置一个唯一的标识, 目前是用 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 是没有作用的.
暂未找到实现文章开头需求的方法, 有知道的大神请留言
来源: http://www.bubuko.com/infodetail-3148279.html