# 04.SpringBoot-Web开发2-响应处理
# 1.响应处理-ReturnValueHandler原理
返回值处理器ReturnValueHandler总共有15个

现在直接看看重点:
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
...
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
...
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {//<----关注点
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
...
invocableMethod.invokeAndHandle(webRequest, mavContainer);//看下块代码
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
...
try {
//看下块代码
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.挑选合适的处理器,看是否支持这种类型的处理supportReturnType()
2.返回处理器调用handleReturnValue进行处理
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
...
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//selectHandler()实现在下面
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
//开始处理
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
@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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
3.@ResponseBody 注解,由RequestResponseBodyMethodProcessor处理,它的父类AbstractMessageConverterMethodProcessor实现HandlerMethodReturnValueHandler接口
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
...
@Override
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);
// 使用消息转换器进行写出操作,本方法下一章节介绍:
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2.响应处理-HTTPMessageConverter原理
回顾一下上面的内容
返回值处理器ReturnValueHandler原理:
1.返回值处理器判断是否支持这种类型的返回值supportsReturnType
2.返回值处理器调用handleReturnValue进行处理
3.RequestResponseBodyMethodProcessor可以处理返回值标了@ResponseBody注解的。
利用MessageConverters进行处理将数据写为json(对应格式,也可能是xml)
内容协商(浏览器会以请求头的方式告诉服务器自己支持什么类型的内容)
服务器最终根据自身能力,决定服务器能产出什么样内容类型的数据
SpringMVC会逐个遍历所有HttpMessageConverter,看谁能处理
得到MappingJackson2HttpMessageConverter可以写对象为json
利用MappingJackson2HttpMessageConverter将对象转json写出去

内容协商表示浏览器支持的数据类型,q代表权重,表示优先接收的数据类型
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler {
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(body, 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) {
HttpMediaTypeNotAcceptableException ex = var20;
int series = outputMessage.getServletResponse().getStatus() / 100;
if (body != null && series != 4 && series != 5) {
throw ex;
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Ignoring error response content (if any). " + ex);
}
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 (this.logger.isDebugEnabled()) {
this.logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(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) {
Object theBody = body;
LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
return "Writing [" + LogFormatUtils.formatValue(theBody, !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");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
HTTPMessageConverter接口:
/**
* Strategy interface for converting from and to HTTP requests and responses.
*/
public interface HttpMessageConverter<T> {
/**
* Indicates whether the given class can be read by this converter.
*/
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
/**
* Indicates whether the given class can be written by this converter.
*/
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
/**
* Return the list of {@link MediaType} objects supported by this converter.
*/
List<MediaType> getSupportedMediaTypes();
/**
* Read an object of the given type from the given input message, and returns it.
*/
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
/**
* Write an given object to the given output message.
*/
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
例子:Person对象转为JSON,或者 JSON转为Person,这将用到MappingJackson2HttpMessageConverter(继承了AbstractJackson2HttpMessageConverter)

ReturnValueHandler与MappingJackson2HttpMessageConverter关联
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
...
@Nullable
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;//我们关注的returnValueHandlers
@Override
@Nullable//本方法在AbstractHandlerMethodAdapter
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
...
mav = invokeHandlerMethod(request, response, handlerMethod);
...
return mav;
}
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {//<---我们关注的returnValueHandlers
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
...
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
...
if (this.returnValueHandlers == null) {//赋值returnValueHandlers
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);
...
// Annotation-based return value types
//这里就是 ReturnValueHandler与 MappingJackson2HttpMessageConverter关联 的关键点
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),//<---MessageConverters也就传参传进来的
this.contentNegotiationManager, this.requestResponseBodyAdvice));//
...
return handlers;
}
//------
public List<HttpMessageConverter<?>> getMessageConverters() {
return this.messageConverters;
}
//RequestMappingHandlerAdapter构造器已初始化部分messageConverters
public RequestMappingHandlerAdapter() {
this.messageConverters = new ArrayList<>(4);
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
if (!shouldIgnoreXml) {
try {
this.messageConverters.add(new SourceHttpMessageConverter<>());
}
catch (Error err) {
// Ignore when no TransformerFactory implementation is available
}
}
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# 3.响应处理-内容协商原理
根据客户端接收能力不同,返回不同媒体类型的数据。
以下依赖,并且设置Accept字段,可以实现返回xml类型数据(配置postman使用)
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2
3
4
原理:
1.当前响应是不是在执行之前已经处理过,是否已经有了明确的MediaType
2.获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段application/xml)(这一步在下一节有详细介绍)
contentNegotiationManager内容协商管理器 默认使用基于请求头的策略HeaderContentNegotiationStrategy确定客户端可以接收的内容类型
3.遍历循环当前系统的MessageConvert看谁支持操作当前对象(Person)
4.找到支持操作Person的转换器convert,将他能支持的媒体类型统计出来
5.根据服务器和客户端能支持的媒体类型进行最佳匹配
6.用支持将对象转为最匹配的类型的convert,调用他进行转化
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler {
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(body, 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) {
HttpMediaTypeNotAcceptableException ex = var20;
int series = outputMessage.getServletResponse().getStatus() / 100;
if (body != null && series != 4 && series != 5) {
throw ex;
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Ignoring error response content (if any). " + ex);
}
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 (this.logger.isDebugEnabled()) {
this.logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(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) {
Object theBody = body;
LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
return "Writing [" + LogFormatUtils.formatValue(theBody, !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");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# 4.响应处理-基于请求参数的内容协商原理
获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段application/xml)
contentNegotiationManager内容协商管理器 默认使用基于请求头的策略HeaderContentNegotiationStrategy确定客户端可以接收的内容类型
//RequestResponseBodyMethodProcessor继承这类
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
implements HandlerMethodReturnValueHandler {
...
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
Object body;
Class<?> valueType;
Type targetType;
...
//本节重点
//内容协商(浏览器默认会以请求头(参数Accept)的方式告诉服务器他能接受什么样的内容类型)
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 = getAcceptableMediaTypes(request);
//服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
...
}
//在AbstractMessageConverterMethodArgumentResolver类内
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
throws HttpMediaTypeNotAcceptableException {
//内容协商管理器 默认使用基于请求头的策略
return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
...
public ContentNegotiationManager() {
this(new HeaderContentNegotiationStrategy());//内容协商管理器 默认使用基于请求头的策略
}
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
Iterator var2 = this.strategies.iterator();
List mediaTypes;
do {
if (!var2.hasNext()) {
return MEDIA_TYPE_ALL_LIST;
}
ContentNegotiationStrategy strategy = (ContentNegotiationStrategy)var2.next();
mediaTypes = strategy.resolveMediaTypes(request);
} while(mediaTypes.equals(MEDIA_TYPE_ALL_LIST));
//MEDIA_TYPE_ALL_LIST是单例集合
//也就是说只要遍历出第一个值就使用该媒体类型对应的第一个策略
return mediaTypes;
}
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {
public HeaderContentNegotiationStrategy() {
}
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
//直接获取Accept请求头信息,里面具体描述了每个媒体类型的权重,
//根据权重匹配所支持的媒体类型,convert处理写出返回
String[] headerValueArray = request.getHeaderValues("Accept");
if (headerValueArray == null) {
return MEDIA_TYPE_ALL_LIST;
} else {
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;
} catch (InvalidMediaTypeException var5) {
InvalidMediaTypeException ex = var5;
throw new HttpMediaTypeNotAcceptableException("Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 开启浏览器参数方式内容协商功能
为了方便内容协商,开启基于请求参数的内容协商功能。
spring:
mvc:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
2
3
4
内容协商管理器,就会多了一个ParameterContentNegotiationStrategy(由Spring容器注入)
public class ParameterContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
private String parameterName = "format";
public ParameterContentNegotiationStrategy(Map<String, MediaType> mediaTypes) {
super(mediaTypes);
}
public void setParameterName(String parameterName) {
Assert.notNull(parameterName, "'parameterName' is required");
this.parameterName = parameterName;
}
public String getParameterName() {
return this.parameterName;
}
@Nullable
protected String getMediaTypeKey(NativeWebRequest request) {
return request.getParameter(this.getParameterName());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
http://localhost:8080/test/person?format=json或
http://localhost:8080/test/person?format=xml
可返回指定媒体类型数据
# 5.响应处理-自定义MessageConverter
实现多协议数据兼容。json、xml、x-guigu(这个是自创的)
1.@ResponseBody响应数据出去调用RequestResponseBodyMethodProcessor处理
2.Processor处理返回值(看是否支持),通过调度MessageConverter 完成处理
3.所有MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
4.内容协商找到最终的messageConverter完成转换
自定义消息转换器
public class GuiguConvert implements HttpMessageConverter<Person> {
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
// clazz是Person或其父类
return clazz.isAssignableFrom(Person.class);
}
/**
* 服务器要统计所有MessageConverter都能写出哪些内容类型
*
* application/x-guigu
* @return
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-guigu");
}
@Override
public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//自定义协议数据的写出
String data=person.getUserName()+";"+person.getAge()+";"+person.getBirth();
// 写出数据
outputMessage.getBody().write(data.getBytes());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
将自定义的转换器,装入converts里
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new GuiguConvert());
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
用Postman发送/test/person(请求头Accept:application/x-guigu),将返回自定义协议数据的写出。
# 6.响应处理-浏览器与PostMan内容协商完全适配
在地址栏输入http://localhost:8080/test/person?format=gg返回数据,跟http://localhost:8080/test/person且请求头参数Accept:application/x-guigu的返回自定义协议数据的一致。
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
HashMap<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("xml", MediaType.APPLICATION_XML);
//自定义媒体类型
mediaTypes.put("gg", MediaType.parseMediaType("application/x-guigu"));
//指定支持解析哪些参数对应的哪些媒体类型
ParameterContentNegotiationStrategy parameterContentNegotiationStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
//还需添加请求头处理策略,否则accept:application/json、application/xml则会失效
HeaderContentNegotiationStrategy headerContentNegotiationStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(parameterContentNegotiationStrategy,headerContentNegotiationStrategy));
}
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
日后开发要注意,有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。
# 请求处理响应全流程总结
整个流程可以宏观地分为三大阶段:定位资源(找谁处理)、绑定参数(准备数据)、响应输出(返回结果)。
# 第一阶段:定位资源(HandlerMapping 与 Rest 映射)
当请求到达时,第一件事是找到哪个Controller里的哪个方法来处理它。
Rest 风格映射
核心注解:
@RequestMapping及其派生注解@GetMapping、@PostMapping、@PutMapping、@DeleteMapping。原理:这些注解是组合注解,本质上是标记了请求方法(Method)的
@RequestMapping。隐含逻辑:Spring 5 之后,配合
HiddenHttpMethodFilter,可以实现浏览器表单(只支持GET/POST)的REST风格。表单提交一个_method=PUT参数,该Filter会将其转换为标准的PUT请求。但是如果是axios发送不需要这个参数。
请求映射原理(HandlerMapping)
核心组件:
DispatcherServlet在初始化时会收集所有@RequestMapping和方法的关系,形成一个映射表(由RequestMappingHandlerMapping维护)。匹配过程:根据请求的 URL、请求方法(GET/POST)、请求头(Headers)、请求参数(Params)、可消费类型(Consumes) 等信息,去映射表中查找。
查找结果:找到唯一的 HandlerMethod(包含了Bean实例和方法信息),并组装上匹配的 拦截器链,最终封装成一个
HandlerExecutionChain。
# 第二阶段:绑定参数(参数解析器与数据模型)
参数解析大体思路:HandlerMapping找能处理请求的Handler,找适配器 HandlerAdapter ,找参数解析器-HandlerMethodArgumentResolver解析
找到方法后,需要把请求中的数据传递给方法。这里出现了两条重要的分支:普通请求参数和@RequestBody。
各种类型参数解析器原理(HandlerMethodArgumentResolver)
我们之前讨论的
HandlerMethodArgumentResolver在这里登场。Spring会遍历所有注册的解析器,找到第一个supportsParameter返回true的解析器,然后调用其resolveArgument来获取参数值。常见的参数解析器场景:
@PathVariable:从URI模板变量中提取。@RequestParam:从请求参数(Query String)中提取。@RequestHeader:从请求头中提取。@CookieValue:从Cookie中提取。@RequestBody:这里走的是另一条路,消息转换器。
Model、Map 与隐含模型
如果你在方法参数中声明了
Model、Map或ModelMap,Spring会把一个隐含的模型对象(BindingAwareModelMap)传递进来。你在方法中对这个
Model添加的任何属性,最终都会被暴露到请求域中,供视图渲染使用(如Thymeleaf模板)。原理:这些参数也是由特定的参数解析器(
ModelMethodProcessor)处理的。值得注意的是,Map和Model实际上引用的是同一个对象,它们共享数据。
参数绑定(数据装配与类型转换)
这是把请求数据填充到JavaBean(如
User对象)的过程,通常由@ModelAttribute触发(ModelAttributeMethodProcessor处理),或直接在参数上使用POJO(ServletModelAttributeMethodProcessor处理)。核心机制:WebDataBinder。
数据获取:
ServletModelAttributeMethodProcessor创建目标对象。类型转换:利用 ConversionService 将字符串转为目标类型。
字段绑定:利用 JavaBean 属性编辑器(PropertyEditor)或Spring的TypeConverter 将值设置到对象的字段上。
数据校验:如果参数有
@Valid或@Validated注解,WebDataBinder会触发校验,结果存到BindingResult中。
# 第三阶段:响应输出(消息转换器与@ResponseBody)
消息转换器原理(HttpMessageConverter)
触发时机:当方法或类上标有
@ResponseBody注解(或者整个类用@RestController)时,Spring不再走视图解析器(ViewResolver),而是使用HttpMessageConverter直接将方法返回值写入HTTP响应体。协商过程:
解析器
RequestResponseBodyMethodProcessor处理返回值。根据请求头的
Accept字段,确定客户端期望的响应格式。遍历所有已注册的
HttpMessageConverter(如MappingJackson2HttpMessageConverter、StringHttpMessageConverter等),调用其canWrite方法,判断是否能处理返回值的类型,如果能处理把他放入getProducibleMediaTypes服务器支持的返回器集合中将客户端期待的响应格式与服务器支持的进行最佳匹配,找到最合适的返回格式
再次遍历
HttpMessageConverter,调用其canWrite找到最合适的Converter,调用其write方法,将对象序列化为JSON、XML或文本等,并写入Response的OutputStream中。
# 宏观流程图
text
请求到达
↓
DispatcherServlet 接收请求
↓
【HandlerMapping】 根据请求查找 HandlerMethod
↓
【HandlerAdapter】 准备执行方法
↓
【参数解析器】 遍历解析器,处理参数 (包含 @ModelAttribute 数据绑定)
├─ 普通参数 (如 @RequestParam) → 直接赋值
├─ Model/Map → 传递隐含模型
└─ @RequestBody → 【消息转换器】反序列化请求体
↓
【执行目标方法】 Controller 中的业务逻辑
↓
【处理返回值】
├─ @ResponseBody / RestController → 【消息转换器】序列化返回值
└─ 返回页面路径 → 【ViewResolver】 视图解析,Model 数据合并
↓
返回响应
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 总结一句话
Spring MVC的请求处理,本质上是DispatcherServlet协调HandlerMapping(定位资源)、HandlerMethodArgumentResolver(参数解析)、HttpMessageConverter(消息转换) 以及HttpMessageConverter(响应输出) 等多个组件,完成从请求数据到Java对象再到响应数据的完整映射过程。