在HttpMessageConverter的註冊咱們知道了: DispatcherServlet#service-->DispatcherServlet#doService-->DispatcherServlet#doDispatch-->HandlerAdapter#handle-->AbstractHandlerMethodAdapter#handleInternalhtml
這裏咱們看一個更加具體的調用流程圖:java
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。