HttpMessageConverter的使用

HttpMessageConverter的注册咱们知道了: DispatcherServlet#service-->DispatcherServlet#doService-->DispatcherServlet#doDispatch-->HandlerAdapter#handle-->AbstractHandlerMethodAdapter#handleInternalhtml

这里咱们看一个更加具体的调用流程图:java

HttpMessageConverter使用

RequestMappingHandlerAdapter继承了AbstractHandlerMethodAdapter,因此当使用RequestMappingHandlerAdapter的时候,最终调用的就是RequestMappingHandlerAdapter的handleInternal函数。web

RequestMappingHandlerAdapter的handleInternal函数调用了,RequestMappingHandlerAdapter的invokeHandlerMethod。服务器

incokeHandlerMethod函数中建立了一个ServletInvocableHandlerMethodapp

ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            if (this.argumentResolvers != null) {
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }

如上所示是RequestMappingHandlerAdapter的incokeHandlerMethod函数的部分代码,咱们能够看到建立ServletInvocableHandlerMethod的时候传入的参数类型是: HandlerMethodArgumentResolverComposite HandlerMethodReturnValueHandlerCompositeide

HandlerMethodArgumentResolverComposite、HandlerMethodReturnValueHandlerComposite是在RequestMappingHandlerAdapter初始化的时候建立的是默认值。能够看RequestMappingHandlerAdapter的afterPropertiesSet函数。函数

HandlerMethodArgumentResolverComposite和HandlerMethodReturnValueHandlerComposite是能够从新设置的,可是必须是使用List<HandlerMethodArgumentResolver>和List<HandlerMethodReturnValueHandler>这样列表的形式,不支持单个添加。this

Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
this.returnValueHandlers.handleReturnValue(
                    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

如上是ServletInvocableHandlerMethod的invokeAndHandle函数的部分代码,invokeForRequest方法是执行的真正的调用逻辑部分,通常也就是Controller中的方法封装为InvocableHandlerMethod,InvocableHandlerMethod的doInvoke执行调用,若是有桥接方法,就调用的桥接方法。spa

另外,咱们能够看到调用的是HandlerMethodReturnValueHandlerComposite的handleReturnValue,这算是比较接近抽象层了。.net

HandlerMethodReturnValueHandlerComposite的逻辑就很是清晰,handleReturnValue函数就是检查List<HandlerMethodReturnValueHandler>列表中哪个HandlerMethodReturnValueHandler支持处理返回值(经过调用HandlerMethodReturnValueHandler接口的supportsReturnType函数)。

最多见的咱们使用ResponseBody注解的时候就会使用到的HandlerMethodReturnValueHandler实现类RequestResponseBodyMethodProcessor。

这里咱们就看使用最多的RequestResponseBodyMethodProcessor的handleReturnValue函数。

它调用了AbstractMessageConverterMethodProcessor的writeWithMessageConverters函数,如今咱们重点的来看一下AbstractMessageConverterMethodProcessor的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();
        if (contentType != null && contentType.isConcrete()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Found 'Content-Type:" + contentType + "' in response");
            }
            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);
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("No match for " + acceptableTypes + ", supported: " + 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 (logger.isDebugEnabled()) {
                logger.debug("Using '" + selectedMediaType + "', given " +
                        acceptableTypes + " and supported " + producibleTypes);
            }
        }

        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) {
            throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
        }
    }

如上所示是AbstractMessageConverterMethodProcessor的writeWithMessageConverters函数,比较长,可见逻辑是比较复杂的。

虽然有点复杂,可是流程仍是很是清晰的,开始先检查和获取返回值的类型和返回值目标类型。

接下来是肯定肯定使用MediaType,首先:

MediaType contentType = outputMessage.getHeaders().getContentType();

从Response的Header中找有没有设置Content-Type,若是有就直接使用该Content-Type对应的MediaType。

若是没有就先获取请求接受的MediaType,就是从请求头中的Accept中解析MediaType:

List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);

再找服务器端支持的MediaType:

List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

查找的方式就是先从Request找看有没有设置MediaType,若是没有设置就找HttpMessageConverter列表中支持的全部MediaType。

而后就一次匹配,就是找到全部客户端可以Accept的MediaType,而且服务端也支持的:

for (MediaType requestedType : acceptableTypes) {
                for (MediaType producibleType : producibleTypes) {
                    if (requestedType.isCompatibleWith(producibleType)) {
                        mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
                    }
                }
            }

若是一个都没有找到,而且返回值不为null,那么就直接抛出异常。就是比较常见的HttpMediaTypeNotAcceptableException异常。

上面一波操做可能找到多个MediaType,咋整呢?排个序。

MediaType.sortBySpecificityAndQuality(mediaTypesToUse);

咱们常常在Accept中看到下面的内容:

text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8

以前一直不知道q=0.8这类的东西是干什么用的,如今看源码就知道了,排序用的,就是设置优先级,q就是quality的简写。

找到一个MediaType就又能够开始愉快的玩耍了,就是遍历最开始咱们设置的HttpMessageConverter列表。找到一个支持MediaType的Converter。

咋找呢HttpMessageConverter的canWrite接口就是用来干这事情的。

找到了HttpMessageConverter接口的write就有用武之地了。固然得先看一下有没有RequestResponseBodyAdvice这种东西,有的话就先执行一下RequestResponseBodyAdvice的beforeBodyWrite。

相关文章
相关标签/搜索