开始
上一节分析到springmvc把参数名称和参数值解析出来后通过反射执行controller得到返回值,接下来要做的就是根据拿到的返回值然后匹配对应的ReturnValueHandler来解析返回值,如下图:
今天就是要分析源码的点就是,如何匹配返回值处理器,返回值处理器是如何解析的,里面还有管内容协商的处理。
1、这里的属性returnValueHandlers是从哪里来的呢?
这个invokeAndHandle方法是在ServletInvocableHandlerMethod类中的
在之前的分析中我们知道这个类的returnValueHandlers属性来至于RequestMappingHandlerAdapter里面,如下图:
2、返回值处理器是根据什么原则来匹配的呢?
进入到handleReturnValue方法,如下图:
这个方法是在返回值处理器组合对象里面的方法,所有的返回值处理都要先调用组合对象里面的方法作为统一入口,这就是组合设计模式,在spring里面很多地方都有这种设计模式。
显然,这个方法的HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
这行代码是选择处理器的逻辑,进入这个方法看看:
第一行代码是异步模式的才会起作用,我们这里不是异步模式先不管,这里的大致逻辑就是,循环遍历每个ReturnValueHandler,然后调用每个ReturnValueHandler的supportsReturnType方法来判断,这个返回值处理器是否支持当前返回值,所以核心匹配逻辑就是每个处理器的supportsReturnType方法。我这里的controller方法是用@ResponseBody修饰的,返回值处理器都是匹配到的RequestResponseBodyMethodProcesor。不同的返回值匹配的到的处理器可能不同,supportsReturnType方法的逻辑也不同,这里就以这个返回值处理器为例,进入supportsReturnType方法分析源码:
这两行代码就很明显了,就是看返回值类型所在类和所在方法是否有ResponseBody注解。
3、返回值处理器是如何处理返回值的呢?
我们这里就以RequestResponseBodyMethodProcesor为例分析,进入到RequestResponseBodyMethodProcesor类的handleReturnValue方法:
@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);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
前三行代码没啥好解释的,第二三行代码就是把request和response包装了下。直接进入writeWithMessageConverters方法吧:
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
Object outputValue;
Class<?> valueType;
Type declaredType;
if (value instanceof CharSequence) {
outputValue = value.toString();
valueType = String.class;
declaredType = String.class;
}
else {
outputValue = value;
valueType = getReturnValueType(outputValue, returnType);
declaredType = getGenericType(returnType);
}
if (isResourceType(value, returnType)) {
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null) {
Resource resource = (Resource) value;
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
outputValue = HttpRange.toResourceRegions(httpRanges, resource);
valueType = outputValue.getClass();
declaredType = RESOURCE_REGION_LIST_TYPE;
}
catch (IllegalArgumentException ex) {
outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
}
}
}
List<MediaType> mediaTypesToUse;
MediaType contentType = outputMessage.getHeaders().getContentType();
if (contentType != null && contentType.isConcrete()) {
mediaTypesToUse = Collections.singletonList(contentType);
}
else {
HttpServletRequest request = inputMessage.getServletRequest();
//就是从request的header里面取ACCEPT字段的值
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
//如果在controller中指定了produces就直接从request里面取出来,
// 否则就根据返回值和返回值的类型匹配可以解析的converter然后把converter支持的媒体类型都返回
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
if (outputValue != null && producibleMediaTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (mediaTypesToUse.isEmpty()) {
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
}
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
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(declaredType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
outputValue = getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage);
}
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + converter + "]");
}
}
return;
}
}
}
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
这个方法实在是太长了,都超出了idea的方法长度限制了,看来springmvc的相关作者有时候也不是很规范。
前面代码没有太多核心逻辑,我们直接进入内容协商的核心逻辑吧:
MediaType contentType = outputMessage.getHeaders().getContentType();
if (contentType != null && contentType.isConcrete()) {
mediaTypesToUse = Collections.singletonList(contentType);
}
else {
HttpServletRequest request = inputMessage.getServletRequest();
//就是从request的header里面取ACCEPT字段的值
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
//如果在controller中指定了produces就直接从request里面取出来,
// 否则就根据返回值和返回值的类型匹配可以解析的converter然后把converter支持的媒体类型都返回
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
if (outputValue != null && producibleMediaTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (mediaTypesToUse.isEmpty()) {
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
}
1、首先会从response里面取出content-type,如果我们在代码里面设置了content-type的内容,代码直接进入第一个分支,那么用于返回的媒体类型就被强制规定为content-type所指定的媒体类型。
2、一般我们都不会在代码里面设置content-Type,所以代码会进入else分支。
3、这个分支里面前面的代码我都写了注释,现在解释一下:
mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
这段代码。这里其实就是根据浏览器ACCEPT字段先取到浏览器能支持的媒体类型,然后根据produces配置的输出媒体类型,或者是converter支持的媒体类型取到能输出的媒体类型,它们取交集,就是本次可以输出的媒体类型,把可以输出的媒体类型放到mediaTypesToUse集合里面,后面要用到这些媒体类型。
4、既然得到了可以使用的媒体类型,那么总要根据一定规则得到一个最合适的媒体类型吧?
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
循环可用媒体类型计算得到,第一个if分支,如果是确定的媒体类型(就是没有带*的)就直接赋值给selectedMediaType变量,就是本次内容协商选择的媒体类型。
5、得到了最终的媒体类型,那么就可以处理返回值了
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(declaredType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
outputValue = getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage);
}
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + converter + "]");
}
}
return;
}
}
}
现在的问题就是用哪个转换器,怎么转化的问题了。
1、用哪个转行器呢?
首先循环所有的转化器,匹配它是否能写这个变量。我这里返回值是一个string类型,所以最终匹配到的就是一个StringHttpMessageConverter转换器。
进入它的canWrite方法看看它是如何判断是否匹配这个值的呢?
其实就两个逻辑:
(1)是不是支持这个返回值类型
(2)是不是支持这个返回值的媒体类型。
接下来匹配到了返回值转换器,第一步先执行controller的切面逻辑(这个不是这里的重点,就先不解析了),然后就是真正的转化返回值了。
2、如何转化的呢?
进入到StringHttpMessageConverter的write方法:
public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> writeInternal(t, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}));
}
else {
writeInternal(t, outputMessage);
outputMessage.getBody().flush();
}
}
这里显然我们不是流式处理所以走得是else逻辑,我们再进入到writeInternal方法:
@Override
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
if (this.writeAcceptCharset) {
outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
}
Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
StreamUtils.copy(str, charset, outputMessage.getBody());
}
这里就很显而易见了,就是把返回值的byte数组直接写入到respose的body里面。整个处理流程核心逻辑就完成了。后面还有一些后续处理,但是基本上都是没有什么逻辑要执行的,就是判断一下response是否已经被处理了,是否还要处理,是否还要渲染页面,我们这个流程,明显后面的都不需要处理了。
总结
总结起来返回值处理就以下步骤:
1、匹配返回值处理器
2、内容协商,确定最终媒体类型
3、匹配返回值转行器
4、通过转换器把返回值转化到response里面。