@ResponseBody注解,标记为此注解的controller,method,return,返回数据将被转换成Json格式数据给客户端
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
具体调用代码流程如下:
DispatcherServlet.doDispatch->
RequestMappingHandlerAdapter.handle
->RequestMappingHandlerAdapter.handleInternal
->RequestMappingHandlerAdapter.invokeHandlerMethod
->ServletInvocableHandlerMethod.invokeAndHandle
->HandlerMethodReturnValueHandlerComposite.handleReturnValue
->HandlerMethodReturnValueHandlerComposite.selectHandler
->RequestResponseBodyMethodProcessor.handleReturnValue
->RequestResponseBodyMethodProcessor.writeWithMessageConverters
->MappingJackson2HttpMessageConverter.write
HandlerMethodReturnValueHandlerComposite的方法selectHandler将调用所有HandlerMethodReturnValueHandler的supportsReturnType方法查找符合要求的处理器。
@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = this.isAsyncReturnValue(value, returnType);
Iterator var4 = this.returnValueHandlers.iterator();
HandlerMethodReturnValueHandler handler;
do {
do {
if (!var4.hasNext()) {
return null;
}
handler = (HandlerMethodReturnValueHandler)var4.next();
} while(isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler));
} while(!handler.supportsReturnType(returnType));
return handler;
}
如果controller中包含@ResponseBody,那么RequestResponseBodyMethodProcessor将被引用,如下代码:
RequestResponseBodyMethodProcessor:
public boolean supportsReturnType(MethodParameter returnType) {
return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class);
}
RequestResponseBodyMethodProcessor的writeWithMessageConverters将过滤所有的处转换器,根据request的MediaType去找到可用的converter, 默认标记为@ResonseBody使用MappingJackson2HttpMessageConverter将返回数据转换成json.
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<MediaType> acceptableTypes = this.getAcceptableMediaTypes(request);
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;
label159: {
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
Iterator var22 = this.messageConverters.iterator();
while(var22.hasNext()) {
converter = (HttpMessageConverter)var22.next();
genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null;
if (genericConverter != null) {
if (((GenericHttpMessageConverter)converter).canWrite((Type)targetType, valueType, selectedMediaType)) {
break label159;
}
} else if (converter.canWrite(valueType, selectedMediaType)) {
break label159;
}
}
}
if (body != null) {
Set<MediaType> producibleMediaTypes = (Set)inputMessage.getServletRequest().getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!isContentTypePreset && CollectionUtils.isEmpty(producibleMediaTypes)) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
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");
}
}
MappingJackson2HttpMessageConverter使com.fasterxml.jackson.databind.ObjectMapper转换Object到Json, 所有我们可以配置Jackson的一些属性值,
如:spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
应用启动时spring autoconfigure jar中JacksonAutoConfiguration会将这些配置载入到JacksonProperties中,Jackson2ObjectMapperBuilder将组装这些配置注入到ObjectMapper中。
@ConfigurationProperties(
prefix = "spring.jackson"
)
public class JacksonProperties {
private String dateFormat;
private String propertyNamingStrategy;
private final Map<PropertyAccessor, Visibility> visibility = new EnumMap(PropertyAccessor.class);
private final Map<SerializationFeature, Boolean> serialization = new EnumMap(SerializationFeature.class);
private final Map<DeserializationFeature, Boolean> deserialization = new EnumMap(DeserializationFeature.class);
private final Map<MapperFeature, Boolean> mapper = new EnumMap(MapperFeature.class);
private final Map<Feature, Boolean> parser = new EnumMap(Feature.class);
private final Map<com.fasterxml.jackson.core.JsonGenerator.Feature, Boolean> generator = new EnumMap(com.fasterxml.jackson.core.JsonGenerator.Feature.class);
private Include defaultPropertyInclusion;
private TimeZone timeZone = null;
private Locale locale;
.....
}