spring mvc @RequestBody @ResponseBoy(@RestController)原理解析

前言

    在spring mvc原理(十一)(十二)介绍了spring mvc如何使用HandlerAdpter对不同类型的请求进行适配处理,调用业务层Controller中的请求处理方法,并最终获取处理结果,设置返回值。按照传统的spring mvc的设计,返回结果还会被传递给视图解析器,进行视图的解析和渲染,最终页面数据被返回给浏览器。目前采用RESTful服务的模式(一般使用@RestController注解)与此不同。在Controller层处理请求之后,处理结果可以直接被设置成json字符串的格式,然后返回给浏览器,整个请求的处理流程在此已经终结。这个过程的实现,依赖原理十二中提到的既是返参数解析器又是返回值处理器的RequestResponseBodyMethodProcessor类。
    为了使没有研究过HandlerAdapter组件原理同时也没有读过spring mvc原理(十一)(十二)的读者理解本文,我这里简要回顾一下的原理十二的内容。
tomcat + spring mvc原理(十二):spring mvc请求的适配处理和返回2-detailprocess.png
请求request在HandlerAdapter组件中的整个处理流程如上图所示,本文将要涉及的部分是处理请求和返回结果这个两个流程。这两流程的实现都是在ServletInvocableHandlerMethod类中。其中

Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);

解析并获取了request中的参数,实现方式是使用参数解析器的管理器HandlerMethodArgumentResolverComposite获取对应参数解析器,并使用其resolveArgument方法解析参数。参数解析之后,使用反射获取Controller的请求处理方法,设置入参后执行。获得的返回结果由返回值处理器的管理器HandlerMethodReturnValueHandlerComposite的对象调用相应返回值处理器的handleReturnValue方法处理。详细的流程还是最好参考上面两篇文章。

作为参数解析器

适配参数解析器

    既然HandlerMethodArgumentResolverComposite是作为参数解析器的管理器,那么通过分析它的代码,就能知道对应的解析器是如何被匹配到的。

//HandlerMethodArgumentResolverComposite.java
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
  //获取参数解析器,方法实现如下
  HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
  if (resolver == null) {
    throw new IllegalArgumentException("Unsupported parameter type [" +
        parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
  }
  //执行参数值解析
  return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
  HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
  if (result == null) {
    for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
      //具体通过记录的参数解析器的supportsParameter方法判断
      if (resolver.supportsParameter(parameter)) {
        result = resolver;
        this.argumentResolverCache.put(parameter, result);
        break;
      }
    }
  }
  return result;
}

通过上面分析上面代码,可以得出结论,HandlerMethodArgumentResolverComposite还是通过自身持有的参数解析器的supportsParameter方法来判断是否支持某类型请求的解析,然后使用该参数解析器的resolveArgument方法获取参数值。
    上文中“既是返参数解析器又是返回值处理器的RequestResponseBodyMethodProcessor是用来解析@RestController注解的控制器类中的方法”这个论断其实并不完全准确,作为参数解析器的RequestResponseBodyMethodProcessor的supportsParameter方法会识别的并不是@RestController注解,而是@RequestBody注解。

//RequestResponseBodyMethodProcessor.java
@Override
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(RequestBody.class);
}

所以如果一个请求request并不是POST而是GET类型,那么参数值的解析还是使用原理十二中介绍的RequestParamXXX类型的参数解析器。

参数解析器运作

    下面来分析一下RequestResponseBodyMethodProcessor如何解析被@RequestBody注解的参数的值。首先入口已经很明确,是RequestResponseBodyMethodProcessor的resolveArgument方法。

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
  //参数的可选设置。即参数值可以为null或者不传
  parameter = parameter.nestedIfOptional();
  //核心代码,使用MessageConverters解析参数
  Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
  //获取参数变量名
  String name = Conventions.getVariableNameForParameter(parameter);

  if (binderFactory != null) { //判断是否有Binder工厂
    //获取binder
    WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
    if (arg != null) {
      //@Valid注解的参数检验
      validateIfApplicable(binder, parameter);
      if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
        throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
      }
    }
    if (mavContainer != null) {
      mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
    }
  }
   //参数如果是可选的,进行Optional相关处理
  return adaptArgumentIfNecessary(arg, parameter);
}

resolveArgument方法主要处理了三个问题: 1.参数为Optional类型
2.使用MessageConverters解析参数 3.binder的相关处理。
参数如为Optional类型,则参数可以为null或者不传,这个在java 8引入,有兴趣可以去搜索一下。至于binder,在原理十二已经有相关介绍,主要是对参数进行预处理,详细可以参考相关内容。最重要的还是readWithMessageConverters方法,只是在RequestResponseBodyMethodProcessor类中的readWithMessageConverters方法只是做了一下参数准备,并没有实质内容,而是调用了父类AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法。这里我把原理十二贴的RequestResponseBodyMethodProcessor的继承图谱再贴一遍,作为参考。
tomcat + spring mvc原理(十二):spring mvc请求的适配处理和返回2-bodyresolver.png

//AbstractMessageConverterMethodArgumentResolver.java
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
    Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

  MediaType contentType;
  boolean noContentType = false;
  try {
    //获取请求体中的内容类型ContentType
    contentType = inputMessage.getHeaders().getContentType();
  }
  catch (InvalidMediaTypeException ex) {
    throw new HttpMediaTypeNotSupportedException(ex.getMessage());
  }
  if (contentType == null) {
    noContentType = true;
    contentType = MediaType.APPLICATION_OCTET_STREAM;
  }
  //获取参数的包含的类类型
  Class<?> contextClass = parameter.getContainingClass();
  Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
  if (targetClass == null) {
    ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
    targetClass = (Class<T>) resolvableType.resolve();
  }
   //获取请求类型
  HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
  Object body = NO_VALUE;

  EmptyBodyCheckingHttpInputMessage message;
  try {
    message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
    //核心:遍历注册的HttpMessageConverter解析请求体
    for (HttpMessageConverter<?> converter : this.messageConverters) {
      Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
      GenericHttpMessageConverter<?> genericConverter =
          (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
      if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
          (targetClass != null && converter.canRead(targetClass, contentType))) {
        if (message.hasBody()) {
          HttpInputMessage msgToUse =
              getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
          body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
              ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
          body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
        }
        else {
          body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
        }
        break;
      }
    }
  }
  catch (IOException ex) {
    throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
  }
  //如果没有参数解析,body依然为NO_VALUE
  if (body == NO_VALUE) {
    if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
        (noContentType && !message.hasBody())) {
      return null;
    }
    throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
  }

  MediaType selectedContentType = contentType;
  Object theBody = body;
  LogFormatUtils.traceDebug(logger, traceOn -> {
    String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
    return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
  });

  return body;
}

    readWithMessageConverters方法中最核心的代码是遍历注册的HttpMessageConverter解析请求体。对于POST请求体中的json数据,如果引入 jackson 相关包,SpringMVC 启动时会配置MappingJackson2HttpMessageConverter。同样的,如果引入的是fastjson的包,就会配置FastJsonHttpMessageConverter,你可以在fastjson包里找到这个类。也可以自行实现HttpMessageConverter接口,定制自己的请求体转换器,这个接口也并不复杂。

public interface HttpMessageConverter<T> {

	boolean canRead(Class<?> clazz, MediaType mediaType);

	boolean canWrite(Class<?> clazz, MediaType mediaType);

	List<MediaType> getSupportedMediaTypes();

	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

作为返回值处理器

适配返回值处理器

    返回值处理器由HandlerMethodReturnValueHandlerComposite适配,这个类的处理逻辑和HandlerMethodArgumentResolverComposite类似。

//HandlerMethodReturnValueHandlerComposite.java
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
    ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
  //获取适配的返回值处理器,实现方法如下
  HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
  if (handler == null) {
    throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
  }
  //使用适配的返回值处理器处理
  handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
  boolean isAsyncValue = isAsyncReturnValue(value, returnType);
  //通过具体的返回值处理器的supportsReturnType方法判断。
  for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
    if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
      continue;
    }
    if (handler.supportsReturnType(returnType)) {
      return handler;
    }
  }
  return null;
}

HandlerMethodReturnValueHandlerComposite的selectHandler方法遍历自身持有的返回值处理器,使用返回值处理器的supportsReturnType方法判断是否支持该类型。然后使用适配到的处理器的handleReturnValue方法处理返回值。
    对应到RequestResponseBodyMethodProcessor返回值处理器,

//RequestResponseBodyMethodProcessor.java
@Override
public boolean supportsReturnType(MethodParameter returnType) {
  return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
      returnType.hasMethodAnnotation(ResponseBody.class));
}

supportsReturnType方法中会检查Controller的接口方法是否带有@ResponseBody注解。需要注意的是@RestController注解是由@ResponseBody注解和@Controller注解复合而成,这也是它为什么同样由RequestResponseBodyMethodProcessor处理返回值的原因。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
	@AliasFor(annotation = Controller.class)
	String value() default "";
}
返回值处理器运作

    RequestResponseBodyMethodProcessor的handleReturnValue方法用来处理@RestController注解的接口方法的返回值。

//RequestResponseBodyMethodProcessor.java
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
    ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

  mavContainer.setRequestHandled(true);
  ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
  ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

  writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

在handleReturnValue方法中,只是对参数做了转换,为处理做准备,具体处理的工作由父类AbstractMessageConverterMethodProcessor的writeWithMessageConverters方法实现。writeWithMessageConverters方法代码量比较大,但是核心代码也是遍历和使用HttpMessageConverter的部分。

//AbstractMessageConverterMethodProcessor.java
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
    ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

  Object body;
  Class<?> valueType;
  Type targetType;
  ······
  //遍历HttpMessageConverter处理返回值
  if (selectedMediaType != null) {
    selectedMediaType = selectedMediaType.removeQualityValue();
    for (HttpMessageConverter<?> converter : this.messageConverters) {
      GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
          (GenericHttpMessageConverter<?>) converter : null);
      if (genericConverter != null ?
          ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
          converter.canWrite(valueType, selectedMediaType)) {
        body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
            (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
            inputMessage, outputMessage);
        if (body != null) {
          Object theBody = body;
          LogFormatUtils.traceDebug(logger, traceOn ->
              "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
          addContentDispositionHeader(inputMessage, outputMessage);
          if (genericConverter != null) {
            genericConverter.write(body, targetType, selectedMediaType, outputMessage);
          }
          else {
            ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
          }
        }
        else {
          if (logger.isDebugEnabled()) {
            logger.debug("Nothing to write: null body");
          }
        }
        return;
      }
    }
  }
 ......
}

最终处理是使用HttpMessageConverter的write方法进行JSON数据的格式化和应答的返回。
    这里以fastjson为例,来看看json数据最终是如何被返回给浏览器的。

//FastJsonHttpMessageConverter.java
public void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    //父类write会调用模板方法writeInternal
    super.write(o, contentType, outputMessage);
}
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    //创建数组字节流缓冲对象
    ByteArrayOutputStream outnew = new ByteArrayOutputStream();

    //参数的准备,理解参数涉及到fastjson原理了,这里不赘述
    try {
        HttpHeaders headers = outputMessage.getHeaders();
        SerializeFilter[] globalFilters = this.fastJsonConfig.getSerializeFilters();
        List<SerializeFilter> allFilters = new ArrayList(Arrays.asList(globalFilters));
        boolean isJsonp = false;
        //这里是作者一个彩蛋,对返回值进行兼容处理,应对
        //com.fasterxml.jackson.databind.node.ObjectNode的类型。
        //自嘲为Strange Code(Q. Q)
        Object value = this.strangeCodeForJackson(object);
        if (value instanceof FastJsonContainer) {
            FastJsonContainer fastJsonContainer = (FastJsonContainer)value;
            PropertyPreFilters filters = fastJsonContainer.getFilters();
            allFilters.addAll(filters.getFilters());
            value = fastJsonContainer.getValue();
        }

        if (value instanceof MappingFastJsonValue) {
            if (!StringUtils.isEmpty(((MappingFastJsonValue)value).getJsonpFunction())) {
                isJsonp = true;
            }
        } else if (value instanceof JSONPObject) {
            isJsonp = true;
        }
        //返回值的json格式化
        int len = JSON.writeJSONString(outnew, this.fastJsonConfig.getCharset(), value, this.fastJsonConfig.getSerializeConfig(), (SerializeFilter[])allFilters.toArray(new SerializeFilter[allFilters.size()]), this.fastJsonConfig.getDateFormat(), JSON.DEFAULT_GENERATE_FEATURE, this.fastJsonConfig.getSerializerFeatures());
        //设置Httpeader
        if (isJsonp) {
            headers.setContentType(APPLICATION_JAVASCRIPT);
        }

        if (this.fastJsonConfig.isWriteContentLength()) {
            headers.setContentLength((long)len);
        }
        //将转换后的outnew写入输出流对象,意味着向客户端发出请求的应答
        outnew.writeTo(outputMessage.getBody());
    } catch (JSONException var14) {
        throw new HttpMessageNotWritableException("Could not write JSON: " + var14.getMessage(), var14);
    } finally {
        outnew.close();
    }
}

    FastJsonHttpMessageConverter的write方法调用了父类的write方法,而父类中的write方法又调用了writeInternal模板方法,所以又回到了子类的writeInternal方法的实现。在上面的代码中,重点包括返回值的json格式化和写入输出流对象。返回值的json格式化借助字节流缓冲对象,使用了fastjson的JSON类的静态方法writeJSONString。最后outputMessage.getBody()获取了ServletResponse的输出流,

public class ServletServerHttpResponse implements ServerHttpResponse {

	private final HttpServletResponse servletResponse;
  .....
	@Override
	public OutputStream getBody() throws IOException {
		this.bodyUsed = true;
		writeHeaders();
		return this.servletResponse.getOutputStream();
	}
  .....
}

格式化后的json数据返回值就直接被写入输出流,返回给了客户端。整个请求和应答的过程在这里完成了。

后记

    RESTful服务的设计使得spring mvc只需要关注后端的处理逻辑,而不再需要关心前端的实现细节。这样也导致在参数值设置之后,spring mvc的视图解析相关的组件已经几乎不再能够发挥作用,表现在返回值处理后,其实整个请求和应答的过程已经完成了。这是历史发展、技术进步而产生的必然现象。不过,因为spring mvc在架构上的灵活设计,让它能够在技术发展之后,仍能经受住考验,这是我们需要学习的地方。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值