我们在前后端分离模式最常用的返回值就是返回json类型,那SpringMVC
是如何把返回值转为json的?这就需要用到返回值处理器了,所以我们一起来看看它的原理吧。
不知道怎么到这一步的,可以看这篇文章:SpringMVC原理(5)-目标方法的执行
@ResponseBody
@GetMapping("/addUser")
public User addUser(User user) {
return user;
}
@ResponseBody
@GetMapping("/getUser")
public String getUser() {
return "getUser";
}
...
在invokeForRequest()
执行完拿到返回值以后,接下来就是使用返回值处理器来处理返回值了。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 1、执行目标方法,拿到返回值
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 2、利用返回值处理器来处理返回值
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
处理返回值-handleReturnValue()
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 1、找到一个返回值处理器
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
// 2、利用返回值处理器来处理返回值
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
-
selectHandler()
:遍历所有返回值处理器,找到能够处理这种返回值的 -
handleReturnValue()
:利用返回值处理器来处理返回值
1、找到一个返回值处理器-selectHandler()
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
// 是否是异步的
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
// 判断是否支持处理这种返回值类型
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
其实遍历所有的返回值处理器,调用supportsReturnType()
来判断是否支持处理这种返回值类型
默认有15个返回值处理器
2、利用返回值处理器处理返回值-handleReturnValue()
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// 利用 HttpMessageConverter 来处理返回值
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
所以返回值处理器也是利用HttpMessageConverter
来写数据的。所以我们把这个类搞清楚也就明白了。
不知道大家还记得吗,在参数解析这一篇的原理中我们分析了@RequestBody
注解也是由HttpMessageConverter
来处理的
@ResponseBody注解的处理
json的返回值会被这个类处理RequestResponseBodyMethodProcessor
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;
// 获取 Content-Type 的值
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 = 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);
}
}
// 利用 HttpMessageConverter 写出去
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
// 遍历所有 HttpMessageConverter
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
// 判断这种HttpMessageConverter是否支持这种媒体类型
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 {
// 把数据以json方式写出去
((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(this.allSupportedMediaTypes);
}
}
全部流程:
-
获取返回值类型
-
判断返回值是否是流类型(我们返回值也可以写流类型)
-
获取选定的媒体类型
- 获取客户端可以接收的媒体类型:
getAcceptableMediaTypes()
- 获取服务端可以处理的媒体类型:
getProducibleMediaTypes()
- 通过双层for循环拿到兼容的媒体类型,然后最后再通过一个for循环拿到一个最佳匹配
- 获取客户端可以接收的媒体类型:
-
利用
HttpMessageConverter
把返回值写出去
这块逻辑比较复杂,我把它分为下面几步来说。
1、获取客户端可以接收的媒体类型-getAcceptableMediaTypes()
就是获取请求头Accept
的值
这一步用到了内容协商管理器,后面可以单独写一篇文章来说,下面也有一些介绍。
// 利用内容协商管理器解析媒体类型
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
throws HttpMediaTypeNotAcceptableException {
return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
}
// 解析媒体类型
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
for (ContentNegotiationStrategy strategy : this.strategies) {
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
continue;
}
return mediaTypes;
}
return MEDIA_TYPE_ALL_LIST;
}
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
// 获取请求头 Accept 的值
String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
if (headerValueArray == null) {
return MEDIA_TYPE_ALL_LIST;
}
List<String> headerValues = Arrays.asList(headerValueArray);
try {
List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
MediaType.sortBySpecificityAndQuality(mediaTypes);
return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
}
}
内容协商管理器:ContentNegotiationManager
2、获取服务端可以处理的媒体类型-getProducibleMediaTypes()
遍历所有的HttpMessageConverter
,看谁支持处理这种返回值,如果支持就拿到它能支持的媒体类型放入集合中
protected List<MediaType> getProducibleMediaTypes(
HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
// 获取请求域中数据
Set<MediaType> mediaTypes =
(Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<>(mediaTypes);
}
else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<>();
// 遍历所有的 HttpMessageConverter,看哪个能处理,就拿到它能支持的媒体类型放入集合中
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter && targetType != null) {
if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
else if (converter.canWrite(valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}
else {
return Collections.singletonList(MediaType.ALL);
}
}
最终得到的媒体类型:*/*
意味着什么类型都可以接收
3、得到兼容的媒体类型
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;
}
}
最终选择的媒体类型:
4、利用HttpMessageConverter把数据写出去
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
// 1、遍历所有 HttpMessageConverter
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
// 2、判断这种 HttpMessageConverter 是否支持这种媒体类型
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
// 3、ResponseBodyAdvice 类对返回值进行统一处理
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) {
// 4、利用 HttpMessageConverter 把数据写出去
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
// 4、利用 HttpMessageConverter 把数据写出去
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
流程:
-
拿到最终确定的媒体类型:
selectedMediaType
-
遍历所有
HttpMessageConverter
,进行处理-
判断这种
HttpMessageConverter
是否支持这种媒体类型 -
ResponseBodyAdvice
类对返回值进行统一处理 -
利用
HttpMessageConverter
把返回值写出去- 添加默认的响应头
- 利用
ObjectMapper
把数据写出去
-
最终拿到的是MappingJackson2HttpMessageConverter
write()
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
// 1、添加默认的响应头
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}));
}
else {
// 2、利用ObjectMapper把数据写出去
writeInternal(t, type, outputMessage);
// 刷新
outputMessage.getBody().flush();
}
}
// 利用ObjectMapper把数据写出去
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// 获取 Content-Type 的值
MediaType contentType = outputMessage.getHeaders().getContentType();
// 编码
JsonEncoding encoding = getJsonEncoding(contentType);
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
// 拿到 Json 的生成器
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputStream, encoding);
try {
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 objectWriter = (serializationView != null ?
this.objectMapper.writerWithView(serializationView) : this.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();
generator.close();
}
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);
}
}
最终要写出去的数据:
到这里我们的返回值就处理完毕了,最终就能得到一个json类型的返回值。
总结
流程
-
通过
selectHandler()
来选择一个HandlerMethodReturnValueHandler
- json的会由
RequestResponseBodyMethodProcessor
来处理
- json的会由
-
通过找到的返回值处理器来处理返回值
- 调用
writeWithMessageConverters()
利用HttpMessageConverter
来处理 - 通过
ContentNegotiationStrategy
内容协商策略,来看客户端能接收什么媒体类型(默认获取请求头Accept中的内容) - 根据解析的结果,选择最适合的媒体类型来响应客户端。(这可能涉及到优先级排序和质量因子(
q
值)的比较) - 最后通过
HttpMessageConverter
来处理,它又通过Jackson
的ObjectMapper
类来处理
- 调用
RequestResponseBodyMethodProcessor
这个类用来处理@RequestBody
和@ResponseBody
注解
可以看到这个类实现了参数解析器和返回值处理器
结构:
具体属性:
HandlerMethodReturnValueHandler
返回值处理器:用来处理返回值
public interface HandlerMethodReturnValueHandler {
// 判断当前的 HandlerMethodReturnValueHandler 是否支持处理给定类型的返回值
boolean supportsReturnType(MethodParameter returnType);
// 处理返回值
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
默认有15个返回值处理器
ContentNegotiationManager
内容协商管理器。内容协商是指服务器根据客户端请求头中提供的信息(如Accept
头),决定返回哪种格式的数据的过程。
请求同一个接口,我要json
就给我json格式
,要xml
就给我xml格式
。这就是内容协商。用来做多端内容适配
各种实现
RequestResponseBodyMethodProcessor
:处理@RequestBody
和@ResponseBody
注解的方法。(最重要、最常用)ViewNameMethodReturnValueHandler
:处理返回视图名称的情况。ModelAttributeMethodProcessor
:处理返回模型属性的情况。ExceptionHandlerExceptionResolver
:处理异常并返回适当的响应
HttpMessageConverter
用于处理 HTTP 请求和响应的消息体与 Java 对象之间的转换。
这个接口定义了如何将 Java 对象序列化为 HTTP 消息体内容,以及如何将 HTTP 消息体内容反序列化为 Java 对象。Spring MVC 利用 HttpMessageConverter 实现了对各种数据格式(如 JSON、XML、CSV 等)的支持。
HttpMessageConverter
接口主要包含以下几个方法:
boolean canRead(Class<?> clazz, MediaType mediaType)
:用于判断此转换器是否可以读取指定类型的对象,基于给定的媒体类型(MediaType)。返回 true 如果可以读取,否则返回 false。boolean canWrite(Class<?> clazz, MediaType mediaType)
:用于判断此转换器是否可以写入指定类型的对象,同样基于给定的媒体类型。返回 true 如果可以写入,否则返回 false。List<MediaType> getSupportedMediaTypes()
:返回此转换器支持的所有媒体类型列表。Object read(Class<?> clazz, HttpInputMessage inputMessage)
:从 HTTP 输入消息中读取并转换为 Java 对象。抛出 IOException 或 HttpMessageNotReadableException 如果读取失败。void write(Object obj, MediaType mediaType, HttpOutputMessage outputMessage)
:将 Java 对象写入到 HTTP 输出消息中。抛出 IOException 或 HttpMessageNotWritableException 如果写入失败
各种实现
MappingJackson2HttpMessageConverter
:使用 Jackson 库进行 JSON 数据的读写。ByteArrayHttpMessageConverter
: 支持字节数据读写StringHttpMessageConverter
:支持字符串读写FormHttpMessageConverter
:处理表单数据的读写。ResourceHttpMessageConverter
:用于处理资源文件的读写ResourceRegionHttpMessageConverter
: 支持分区资源写出AllEncompassingFormHttpMessageConverter
:支持表单xml/json读写MappingJackson2HttpMessageConverter
: 支持请求响应体json读写
后面如果可以的话,会单独写文章为这些组件做介绍。