spring mvc @RequestBody @ResponseBoy (@RestController)原理解析
前言
在spring mvc原理(十一)和(十二)介绍了spring mvc如何使用HandlerAdpter对不同类型的请求进行适配处理,调用业务层Controller中的请求处理方法,并最终获取处理结果,设置返回值。按照传统的spring mvc的设计,返回结果还会被传递给视图解析器,进行视图的解析和渲染,最终页面数据被返回给浏览器。目前采用RESTful服务的模式(一般使用@RestController注解)与此不同。在Controller层处理请求之后,处理结果可以直接被设置成json字符串的格式,然后返回给浏览器,整个请求的处理流程在此已经终结。这个过程的实现,依赖原理十二中提到的既是返参数解析器又是返回值处理器的RequestResponseBodyMethodProcessor类。
为了使没有研究过HandlerAdapter组件原理同时也没有读过spring mvc原理(十一)和(十二)的读者理解本文,我这里简要回顾一下的原理十二的内容。
请求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的继承图谱再贴一遍,作为参考。
//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在架构上的灵活设计,让它能够在技术发展之后,仍能经受住考验,这是我们需要学习的地方。