SpringMVC源码(八)- @ResponseBody方式的解析流程

目录

1、handleReturnValue(适配解析结果)

1)、selectHandler(适配返回值解析器-RequestResponseBodyMethodProcessor)

2)、handleReturnValue(解析返回结果)

writeWithMessageConverters(返回值处理)

HttpMessageConverter的write(返回值以输出流输出到客户端或浏览器)

2、getModelAndView

3、上层processDispatchResult(结果处理,回调拦截器的后置方法)

4、总结


    在了解完上一篇之后,我们知道整个Spring MVC流程的调用解析过程,里面很多步骤都相当于插件一样,遍历进行适配,最后是调用。而@ResponseBody修饰的Controller中的方法,是比较常用的一种方式,比如Api类型的接口返回的数据,现在基本都是json类型的。其实与ModelAndView本身没有关系了,获取可以理解成,没有视图(View),并且ModelMap也比较特殊,只有一个值。

    继续回到RequestMappingHandlerAdapterinvokeHandlerMethod方法,在准备好了一切之后,会调用ServletInvocableHandlerMethodinvokeAndHandle方法。在对@ResponseBody方法反射调用后会获取到各种类型的返回值,最后对调用returnValueHandlershandleReturnValue方法。

    在调用之前传入了一个参数(返回值类型),如下:

getReturnValueType(returnValue)
public MethodParameter getReturnValueType(@Nullable Object returnValue) {
    return new ReturnValueMethodParameter(returnValue);
}

    知道new了一个对象,其实可以理解为装饰器模式,将结果进行包装后,拥有了返回值类型等增强功能。

 

1、handleReturnValue(适配解析结果)

@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);
}

    先根据返回值,适配返回值解析器,再调用handleReturnValue方法解析结果。

1)、selectHandler(适配返回值解析器-RequestResponseBodyMethodProcessor

@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

    调用supportsReturnType方法,挨个进行适配。其中还需要判断是否为异常处理类型,当前只分析同步类型。最后适配@ResponseBody类型的为RequestResponseBodyMethodProcessor类型。适配方法如下:

@Override
public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), 
        ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class));
}
  • Controller类上是否有@ResponseBody注解(则所有方法都有效果)

    传入的ReturnValueMethodParametergetContainingClass方法进行判断,先判断内部属性,当下为null。则判断类上是否有该注解。

public Class<?> getContainingClass() {
    Class<?> containingClass = this.containingClass;
    return (containingClass != null ? containingClass : getDeclaringClass());
}
  • 方法本身(HandlerMethod)上面是否有@ResponseBody注解

    如果上面判断类上有则不用判断方法上了,方法HandlerMethod是在调用方法之前在外边就解析好的。直接判断是否有该注解。

@Override
public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) {
    return HandlerMethod.this.hasMethodAnnotation(annotationType);
}

 

2)、handleReturnValue(解析返回结果)

    进入RedirectAttributesMethodArgumentResolverhandleReturnValue方法:

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    // 将处理过的标识设置为true,则后面getModelAndView时会跳过
    mavContainer.setRequestHandled(true);
    // webRequest包装了request和response, 当前只是对请求和返回再进行包装(装饰器模式)
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

    // Try even with null return value. ResponseBodyAdvice could get involved.
    // 将结果进行处理,其实就是用输出流进行输出
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

    1、进入之后,后续会将方法返回的任何类型的结果用OutputStream输入给客户端(浏览器)。所以进来之后直接将处理过的标志设置为true。

    2、将HttpServletRequest和HttpServletResponse的包装类ServletWebRequest,使用装饰器模式包装成ServletServerHttpRequestServletServerHttpResponse,装饰器模式肯定是为了新增功能。为后续的将结果使用输出流输出给客户端(浏览器)做准备。

    3、将返回值进行处理,其实就是用输出流进行输出

 

writeWithMessageConverters(返回值处理)

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
                                              ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    Object body;
    Class<?> valueType;
    Type targetType;
    if (value instanceof CharSequence) {
        body = value.toString();
        valueType = String.class;
        targetType = String.class;
    } else {
        body = value;
        valueType = getReturnValueType(body, returnType);
        targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
    }

    if (isResourceType(value, returnType)) {
        outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
        if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
                outputMessage.getServletResponse().getStatus() == 200) {
            Resource resource = (Resource) value;
            try {
                List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
                outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
                body = HttpRange.toResourceRegions(httpRanges, resource);
                valueType = body.getClass();
                targetType = 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());
            }
        }
    }

    MediaType selectedMediaType = null;
    MediaType contentType = outputMessage.getHeaders().getContentType();
    boolean isContentTypePreset = contentType != null && contentType.isConcrete();
    if (isContentTypePreset) {
        selectedMediaType = contentType;
    } else {
        HttpServletRequest request = inputMessage.getServletRequest();
        List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
        List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

        if (body != null && producibleTypes.isEmpty()) {
            throw new HttpMessageNotWritableException(
                    "No converter found for return value of type: " + valueType);
        }
        List<MediaType> mediaTypesToUse = new ArrayList<>();
        for (MediaType requestedType : acceptableTypes) {
            for (MediaType producibleType : producibleTypes) {
                if (requestedType.isCompatibleWith(producibleType)) {
                    mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
                }
            }
        }
        if (mediaTypesToUse.isEmpty()) {
            if (body != null) {
                throw new HttpMediaTypeNotAcceptableException(producibleTypes);
            }
            return;
        }

        MediaType.sortBySpecificityAndQuality(mediaTypesToUse);

        for (MediaType mediaType : mediaTypesToUse) {
            if (mediaType.isConcrete()) {
                selectedMediaType = mediaType;
                break;
            } else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
                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(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;
            }
        }
    }

    if (body != null) {
        if (isContentTypePreset) {
            throw new HttpMessageNotWritableException(
                    "No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
        }
        throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
    }
}

    1、处理返回值,返回值类型,希望输出的类型(String类型单独进行处理)。

    分别是真实的返回值对象,返回值对象的类型,期望返回的类型是@ResponseBody方法上的返回值类型。比如可能期望返回Map,真实返回的是HashMap。

    2、如返回值类型是Spring的Resource类型,,,,,则对请求头进行处理和检查(忽略)

    3、对Content-Type和MediaType进行处理

1)、如果请求头中有则获取该类型,否则就根据各种情况进行适配

2)、按照适配的类型进行排序

MediaType.sortBySpecificityAndQuality(mediaTypesToUse);

3)、获取排序后最适合的类型

 

    4、适配返回类型,进行处理

    挨个进行适配HttpMessageConverter类型。适配时还与之前或者或者适配的MediaType相关,比如我们现在基本都是常用application/json。在此前提条件下

    比如调用Controller方法返回的值为String类型,则适配到StringHttpMessageConverter类型进行处理。

    如果为其他Map(HashMap)、或者对象等适配到的是MappingJackson2HttpMessageConverter类型进行处理。

 

HttpMessageConverter的write(返回值以输出流输出到客户端或浏览器)

    上面不论是哪种类型,在处理完header后,最后向上转型为父类HttpMessageConverter调用write方法将返回值用输出流返回给客户端(浏览器)。

@Override
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();
    }
}

 

按道理到现在基本完成了@ResponseBody类型的处理,但是框架后续还需要进行一些收尾工作。

2、getModelAndView

    考虑各种情况的处理,所以在getModelAndView对视图和数据进行处理,但是允许对处理过的进行跳过,如下:

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
                                     ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

    modelFactory.updateModel(webRequest, mavContainer);
    if (mavContainer.isRequestHandled()) {
        return null;
    }
    // 省略
}

3、上层processDispatchResult(结果处理,回调拦截器的后置方法)

    虽然前面将值返回给了客户端,但是后续还会执行processDispatchResult方法处理结果,由于没有返回View(视图),所以打印日志后直接跳过。但还是会对所有的HandlerInteceptor的afterCompletion方法进行回调。

 

4、总结

    @ResponseBody注解方式会在handleReturnValue阶段就被RequestResponseBodyMethodProcessor适配处理(适配是先判断类上是否有该注解,否则才会判断方法是否有该注解)。

    将处理过的标识设置为true,后续就会跳过处理;并且对Request和Response进行包装;

    处理结果时,如果HttpServletRequest的header中有返回值类型则不用在适配了(否则还是遍历适配,排序)。

    根据结果适配HttpMessageConverter类型(String类型适配StringHttpMessageConverter),最后调用write方法将返回值用输出流输出给客户端(或者浏览器)。

    不论什么类型,后续在结果处理时,还是会调用HandlerInteceptor的后续处理方法(也就是说该类型是先输出了结果,再处理的拦截器的后置回调方法)。

 

 

 

 

©️2020 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值