目的
@ResponseBody可以添加在控制器类或其下方法中,这样在请求结果对象返回时能够将其解析为JSON格式,这是如何实现的呢?
文章目的在于梳理@ResponseBody实现的原理
引子
先看一段摘自官网文档的内容,https://spring.io/guides/gs/rest-service/
The Greeting object must be converted to JSON. Thanks to Spring’s HTTP message converter support, you need not do this conversion manually.
Because Jackson 2 is on the classpath,Spring’s MappingJackson2HttpMessageConverter is automatically chosen to convert the Greeting instance to JSON.
探究
文档中提及了具体实现Json转换类,可以在JacksonHttpMessageConvertersConfiguration这个配置类中找到mappingJackson2HttpMessageConverter的Bean注入。
@Configuration(
proxyBeanMethods = false
)
class JacksonHttpMessageConvertersConfiguration {
JacksonHttpMessageConvertersConfiguration() {
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({XmlMapper.class})
@ConditionalOnBean({Jackson2ObjectMapperBuilder.class})
protected static class MappingJackson2XmlHttpMessageConverterConfiguration {
protected MappingJackson2XmlHttpMessageConverterConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter(Jackson2ObjectMapperBuilder builder) {
return new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build());
}
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({ObjectMapper.class})
@ConditionalOnBean({ObjectMapper.class})
@ConditionalOnProperty(
name = {"spring.mvc.converters.preferred-json-mapper"},
havingValue = "jackson",
matchIfMissing = true
)
static class MappingJackson2HttpMessageConverterConfiguration {
MappingJackson2HttpMessageConverterConfiguration() {
}
// 关键:这里注入了mappingJackson2HttpMessageConverter,这个类是AbstractJackson2HttpMessageConverter的子类
@Bean
@ConditionalOnMissingBean(
value = {MappingJackson2HttpMessageConverter.class},
ignoredType = {"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter"}
)
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
return new MappingJackson2HttpMessageConverter(objectMapper);
}
}
}
可以看到,上面类中的核心是注入了一个mappingJackson2HttpMessageConverter类,那么接下来就是看一下这个类在哪里被使用了。先来看看这个类的接口实现及继承关系。
这里我们看到最顶层的接口是HttpMessageConverter
,下面的问题就是弄清HttpMessageConverter
的调用过程。
Debug可以发现,数据对象返回后经过了RequestResponseBodyMethodProcessor.handleReturnValue方法,其相关代码如下,可以看到调用了this.writeWithMessageConverters来处理返回的结果。
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);
// 核心调用
this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
上面代码中,最核心的调用方法是writeWithMessageConverters方法,其具体实现如下,该方法的核心调用是converter.write(body, selectedMediaType, outputMessage)
。
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
Object body;
Class valueType;
Object targetType;
if (value instanceof CharSequence) {
body = value.toString();
valueType = String.class;
targetType = String.class;
} else {
body = value;
valueType = this.getReturnValueType(value, returnType);
targetType = GenericTypeResolver.resolveType(this.getGenericType(returnType), returnType.getContainingClass());
}
if (this.isResourceType(value, returnType)) {
outputMessage.getHeaders().set("Accept-Ranges", "bytes");
if (value != null && inputMessage.getHeaders().getFirst("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 var19) {
outputMessage.getHeaders().set("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 (this.logger.isDebugEnabled()) {
this.logger.debug("Found 'Content-Type:" + contentType + "' in response");
}
selectedMediaType = contentType;
} else {
HttpServletRequest request = inputMessage.getServletRequest();
List acceptableTypes;
try {
acceptableTypes = this.getAcceptableMediaTypes(request);
} catch (HttpMediaTypeNotAcceptableException var20) {
int series = outputMessage.getServletResponse().getStatus() / 100;
if (body != null && series != 4 && series != 5) {
throw var20;
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Ignoring error response content (if any). " + var20);
}
return;
}
List<MediaType> producibleTypes = this.getProducibleMediaTypes(request, valueType, (Type)targetType);
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);
}
List<MediaType> mediaTypesToUse = new ArrayList();
Iterator var15 = acceptableTypes.iterator();
MediaType mediaType;
while(var15.hasNext()) {
mediaType = (MediaType)var15.next();
Iterator var17 = producibleTypes.iterator();
while(var17.hasNext()) {
MediaType producibleType = (MediaType)var17.next();
if (mediaType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(this.getMostSpecificMediaType(mediaType, producibleType));
}
}
}
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
var15 = mediaTypesToUse.iterator();
while(var15.hasNext()) {
mediaType = (MediaType)var15.next();
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Using '" + selectedMediaType + "', given " + acceptableTypes + " and supported " + producibleTypes);
}
}
HttpMessageConverter converter;
GenericHttpMessageConverter genericConverter;
label183: {
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
Iterator var23 = this.messageConverters.iterator();
while(var23.hasNext()) {
converter = (HttpMessageConverter)var23.next();
genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null;
if (genericConverter != null) {
if (((GenericHttpMessageConverter)converter).canWrite((Type)targetType, valueType, selectedMediaType)) {
break label183;
}
} else if (converter.canWrite(valueType, selectedMediaType)) {
break label183;
}
}
}
if (body != null) {
Set<MediaType> producibleMediaTypes = (Set)inputMessage.getServletRequest().getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!isContentTypePreset && CollectionUtils.isEmpty(producibleMediaTypes)) {
throw new HttpMediaTypeNotAcceptableException(this.getSupportedMediaTypes(body.getClass()));
}
throw new HttpMessageNotWritableException("No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
}
return;
}
body = this.getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, converter.getClass(), inputMessage, outputMessage);
if (body != null) {
LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
return "Writing [" + LogFormatUtils.formatValue(body, !traceOn) + "]";
});
this.addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(body, (Type)targetType, selectedMediaType, outputMessage);
} else {
// 核心调用
converter.write(body, selectedMediaType, outputMessage);
}
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Nothing to write: null body");
}
}
进一步梳理
上面代码流程可能不是很好理解,这里放出时序图来帮助概览接口返回数据时进行convert的过程。
为了避免调用层级过深导致图过于复杂,时序图只绘制到了HttpMessageConverter接口层级,该接口具体调用哪个类我们来看看下面这张图。
可以看到在AbstractGenericHttpMessageConverter抽象类的调用方法时,转去调用了writeInternal方法。具体调用的实现是AbstractJackson2HttpMessageConverter的writerInternal方法,其具体实现如下
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
MediaType contentType = outputMessage.getHeaders().getContentType();
JsonEncoding encoding = this.getJsonEncoding(contentType);
Class<?> clazz = object instanceof MappingJacksonValue ? ((MappingJacksonValue)object).getValue().getClass() : object.getClass();
ObjectMapper objectMapper = this.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);
Throwable var10 = null;
try {
this.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 = this.getJavaType(type, (Class)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);
this.writeSuffix(generator, object);
generator.flush();
} catch (Throwable var26) {
var10 = var26;
throw var26;
} finally {
if (generator != null) {
if (var10 != null) {
try {
generator.close();
} catch (Throwable var25) {
var10.addSuppressed(var25);
}
} else {
generator.close();
}
}
}
} catch (InvalidDefinitionException var28) {
throw new HttpMessageConversionException("Type definition error: " + var28.getType(), var28);
} catch (JsonProcessingException var29) {
throw new HttpMessageNotWritableException("Could not write JSON: " + var29.getOriginalMessage(), var29);
}
}
MappingJackson2HttpMessageConverter类直接调用父类的writeInternal方法,并未重写,因此实际转换逻辑即为上方代码逻辑。
以上就是@ResponseBody的调用流程及实现原理,根据此调用流程,如果我们想自定义请求返回格式,利用HttpMessageConvert实现是一种可行方案。最后,本文如存在不正确的地方,欢迎并感谢批评指正!