HttpMessageConverter的使用

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

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

HttpMessageConverter使用

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

RequestMappingHandlerAdapter的handleInternal函数调用了,RequestMappingHandlerAdapter的invokeHandlerMethod。

incokeHandlerMethod函数中创建了一个ServletInvocableHandlerMethod

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

如上所示是RequestMappingHandlerAdapter的incokeHandlerMethod函数的部分代码,我们可以看到创建ServletInvocableHandlerMethod的时候传入的参数类型是: HandlerMethodArgumentResolverComposite HandlerMethodReturnValueHandlerComposite

HandlerMethodArgumentResolverComposite、HandlerMethodReturnValueHandlerComposite是在RequestMappingHandlerAdapter初始化的时候创建的是默认值。可以看RequestMappingHandlerAdapter的afterPropertiesSet函数。

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

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

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

另外,我们可以看到调用的是HandlerMethodReturnValueHandlerComposite的handleReturnValue,这算是比较接近抽象层了。

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。

转载于:https://my.oschina.net/u/2474629/blog/3025957

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值