在HttpMessageConverter的注册我们知道了: DispatcherServlet#service-->DispatcherServlet#doService-->DispatcherServlet#doDispatch-->HandlerAdapter#handle-->AbstractHandlerMethodAdapter#handleInternal
这里我们看一个更加具体的调用流程图:
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。