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。

相關文章
相關標籤/搜索