承接上一篇文章,还是按照之前的顺序来debug,先进入invokeAndHandle执行方法,可以看到this.returnValueHandlers是所有可以匹配的返回值处理器,通过supportsReturnType方法判断是否支持该类型的返回值后匹配到了目标的ReturnValueHandler,就可以执行它的handleReturnValue方法,也就是上篇文章中文末提到的处理返回值的方法handleReturnValue(),进入最内层的handleReturnValue方法。
上文也已经分析匹配到了RequestResponseBodyMethodProcessor是处理这个例子的handler(也叫processor,处理器),这个处理器就是处理标注了@ResponseBody注解的方法的,可以处理它的返回值。
进入处理的writeWithMessageConverters方法,传进返回值的值、返回值的类型、输出数据和输出数据。顾名思义,就是利用messageConverters(信息类型转换器)将数据以json的类型写入。
首先我们put的数据不是字符类型的(charSequence),走else部分逻辑。
valueType是我们传入值的类型,targetType是我们目标写入的数据类型。
还可以判断返回值是否为Resource(资源)类型的,如果是就会调用处理流数据(stream)的逻辑。
下面一段看到MediaType,MediaType就涉及到我们的内容协商原理。
内容协商就是浏览器告诉客户端自己能接收什么类型的数据,即浏览器页面的请求头信息中以Accept-(接收)为首的数据:
对于不同的接收类型数据有不同的权重(q)。
可以看到在writeWithMessageConverters方法中具体的处理逻辑也是先获得可以接收的类型(acceptableTypes),而acceptableTypes正好就是浏览器请求头中(Accept-)的信息。
①acceptableTypes是该请求可以接受的类型,一共有七种。(入)
我们不难发现,数组中的七个值正好就是浏览器请求头中(Accept-)的信息。
②producibleTypes是可以生成的类型。(出)
找到了①入(acceptableTypes)和②出(producibleTypes)后,双层for循环匹配①②。
如果还是没有匹配的类型(mediaTypeToUse为空),就会报错或返回debug。
如果成功匹配到,就会生成mediaTypes数组如下:
如无意外,完成处理后会得到最终的一个selectedMediaType(选定了的)。
此时可以看到seletedMediaType的值
然后才进到我们这篇文章的核心——HttpMessageConverter处理返回值。
查看this.messageConverters有哪些值,有哪些converter。
同理,SpringMVC会遍历底层的所有HttpMessageConverter接口,找到合适的converter。
查看HttpMessageConverter总接口:
查看HttpMessageConverter总接口的方法,其中canRead和canWrite方法判断是否支持(Class类型入、MediaType类型出)的读、写操作。在我们这里的例子就是Person类型的对象转为JSON类型,当然也支持JSON转Person类型。视具体要求而定。也说明了转换的过程是可逆的,如果要写出就是Person类型的对象转为JSON,写入则相反。
下面就不同的messageConverter按顺序说明(可参照上几张图):
其中,第7个converter需要注意!!
(7和8都是处理同两种数据类型相互转换的,不过是处理的Encoding编码方式不一样,7是处理UTF-8的,8是处理ISO-8859的——>>后文writeInternal方法中会看到编码格式的处理)
可以查看MappingJacksonHttpMessageConveter接口中:
supports返回值永远为true可以说明, MappingJacksonHttpMessageConveter支持任何类型。(就是兜底的,万金油converter)而且支持的MediaType是以下两种:
注意:这里有一个三元判断符,都是进行能否写入的判断的(canWrite方法):
进入canWrite方法:
(最内层的canWrite方法还是前文看过的。)
最后判断都完成后,调用write方法写入:
然后进入写入的核心方法write():
可以看到headers的值就是Content-Type的值(MappingJacksonHttpMessageConveter指定)
write方法中的处理核心还是writeInternal方法:
最后来到 ,之所以能转成JSON类型,就是通过objectWriter写入数据。
(可用复习一下IO流,要实现写入写出数据,要记得flush一下才能成功。
同时还可以看到outputMessage的内容为:
outputBuffer中存放输出流的数据,可以看到是JSON类型的。即已经将我们的Class类型的数据转为JSON类型的数据输出了。
(可以自己探索下不同类型的converter匹配的是什么类型的数据、注解等,写一些小案例实现)
【补充】下面是writeInternal方法的源码:
@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
MediaType contentType = outputMessage.getHeaders().getContentType();
JsonEncoding encoding = getJsonEncoding(contentType);
Class<?> clazz = (object instanceof MappingJacksonValue ?
((MappingJacksonValue) object).getValue().getClass() : object.getClass());
ObjectMapper objectMapper = selectObjectMapper(clazz, contentType);
Assert.state(objectMapper != null, "No ObjectMapper for " + clazz.getName());
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
try (JsonGenerator generator = objectMapper.getFactory().createGenerator(outputStream, encoding)) {
writePrefix(generator, object);
Object value = object;
Class<?> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;
if (object instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) object;
value = container.getValue();
serializationView = container.getSerializationView();
filters = container.getFilters();
}
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
javaType = getJavaType(type, null);
}
ObjectWriter objectWriter = (serializationView != null ?
objectMapper.writerWithView(serializationView) : objectMapper.writer());
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && javaType.isContainerType()) {
objectWriter = objectWriter.forType(javaType);
}
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}
objectWriter.writeValue(generator, value);
writeSuffix(generator, object);
generator.flush();
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
}
}
最后附上writeWithMessageConverters方法的源码如下:
@SuppressWarnings({"rawtypes", "unchecked"})
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();
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
if (isContentTypePreset) {
if (logger.isDebugEnabled()) {
logger.debug("Found 'Content-Type:" + contentType + "' in response");
}
selectedMediaType = contentType;
}
else {
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> acceptableTypes;
try {
acceptableTypes = getAcceptableMediaTypes(request);
}
catch (HttpMediaTypeNotAcceptableException ex) {
int series = outputMessage.getServletResponse().getStatus() / 100;
if (body == null || series == 4 || series == 5) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring error response content (if any). " + ex);
}
return;
}
throw ex;
}
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 (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(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) {
Set<MediaType> producibleMediaTypes =
(Set<MediaType>) inputMessage.getServletRequest()
.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
throw new HttpMessageNotWritableException(
"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
}
throw new HttpMediaTypeNotAcceptableException(getSupportedMediaTypes(body.getClass()));
}
}