# 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();
        }
    }
1
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) {
            ...
        }
    }
1
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;
    }
1
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);
    }

}
1
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");
        }

    }

}
1
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;

}
1
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());
    }

    ...

}
1
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>
1
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");
        }

    }

}
1
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));
    }

}
1
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;
    }


    ...

}
1
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());
            }
        }
    }
}
1
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  #开启请求参数内容协商模式
1
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());
    }
}
1
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());
    }
}
1
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());
            }
        }
    }
}
1
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));
            } 
        };
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

日后开发要注意,有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。


# 请求处理响应全流程总结

整个流程可以宏观地分为三大阶段:定位资源(找谁处理)绑定参数(准备数据)响应输出(返回结果)

# 第一阶段:定位资源(HandlerMapping 与 Rest 映射)

当请求到达时,第一件事是找到哪个Controller里的哪个方法来处理它。

  1. Rest 风格映射

    • 核心注解@RequestMapping 及其派生注解 @GetMapping@PostMapping@PutMapping@DeleteMapping

    • 原理:这些注解是组合注解,本质上是标记了请求方法(Method)的 @RequestMapping

    • 隐含逻辑:Spring 5 之后,配合 HiddenHttpMethodFilter,可以实现浏览器表单(只支持GET/POST)的REST风格。表单提交一个 _method=PUT 参数,该Filter会将其转换为标准的PUT请求。但是如果是axios发送不需要这个参数。

  2. 请求映射原理(HandlerMapping)

    • 核心组件DispatcherServlet 在初始化时会收集所有 @RequestMapping 和方法的关系,形成一个映射表(由 RequestMappingHandlerMapping 维护)。

    • 匹配过程:根据请求的 URL、请求方法(GET/POST)、请求头(Headers)、请求参数(Params)、可消费类型(Consumes) 等信息,去映射表中查找。

    • 查找结果:找到唯一的 HandlerMethod(包含了Bean实例和方法信息),并组装上匹配的 拦截器链,最终封装成一个 HandlerExecutionChain

# 第二阶段:绑定参数(参数解析器与数据模型)

参数解析大体思路:HandlerMapping找能处理请求的Handler,找适配器 HandlerAdapter ,找参数解析器-HandlerMethodArgumentResolver解析

找到方法后,需要把请求中的数据传递给方法。这里出现了两条重要的分支:普通请求参数和@RequestBody。

  1. 各种类型参数解析器原理(HandlerMethodArgumentResolver)

    • 我们之前讨论的 HandlerMethodArgumentResolver 在这里登场。Spring会遍历所有注册的解析器,找到第一个 supportsParameter 返回 true 的解析器,然后调用其 resolveArgument 来获取参数值。

    • 常见的参数解析器场景

      • @PathVariable:从URI模板变量中提取。

      • @RequestParam:从请求参数(Query String)中提取。

      • @RequestHeader:从请求头中提取。

      • @CookieValue:从Cookie中提取。

      • @RequestBody:这里走的是另一条路,消息转换器

  2. Model、Map 与隐含模型

    • 如果你在方法参数中声明了 ModelMap 或 ModelMap,Spring会把一个隐含的模型对象(BindingAwareModelMap)传递进来。

    • 你在方法中对这个 Model 添加的任何属性,最终都会被暴露到请求域中,供视图渲染使用(如Thymeleaf模板)。

    • 原理:这些参数也是由特定的参数解析器(ModelMethodProcessor)处理的。值得注意的是,Map 和 Model 实际上引用的是同一个对象,它们共享数据。

  3. 参数绑定(数据装配与类型转换)

    • 这是把请求数据填充到JavaBean(如 User 对象)的过程,通常由 @ModelAttribute 触发(ModelAttributeMethodProcessor处理),或直接在参数上使用POJO(ServletModelAttributeMethodProcessor处理)。

    • 核心机制WebDataBinder

      1. 数据获取ServletModelAttributeMethodProcessor 创建目标对象。

      2. 类型转换:利用 ConversionService 将字符串转为目标类型。

      3. 字段绑定:利用 JavaBean 属性编辑器(PropertyEditor)或Spring的TypeConverter 将值设置到对象的字段上。

      4. 数据校验:如果参数有 @Valid 或 @Validated 注解,WebDataBinder 会触发校验,结果存到 BindingResult 中。

# 第三阶段:响应输出(消息转换器与@ResponseBody)

  1. 消息转换器原理(HttpMessageConverter)

    • 触发时机:当方法或类上标有 @ResponseBody 注解(或者整个类用 @RestController)时,Spring不再走视图解析器(ViewResolver),而是使用 HttpMessageConverter 直接将方法返回值写入HTTP响应体。

    • 协商过程

      1. 解析器 RequestResponseBodyMethodProcessor 处理返回值。

      2. 根据请求头的 Accept 字段,确定客户端期望的响应格式。

      3. 遍历所有已注册的 HttpMessageConverter(如 MappingJackson2HttpMessageConverterStringHttpMessageConverter 等),调用其 canWrite 方法,判断是否能处理返回值的类型,如果能处理把他放入getProducibleMediaTypes服务器支持的返回器集合中

      4. 将客户端期待的响应格式与服务器支持的进行最佳匹配,找到最合适的返回格式

      5. 再次遍历HttpMessageConverter,调用其canWrite找到最合适的 Converter,调用其 write 方法,将对象序列化为JSON、XML或文本等,并写入Response的OutputStream中。

# 宏观流程图

text

请求到达
 ↓
DispatcherServlet 接收请求
 ↓
【HandlerMapping】 根据请求查找 HandlerMethod
 ↓
【HandlerAdapter】 准备执行方法
 ↓
【参数解析器】 遍历解析器,处理参数 (包含 @ModelAttribute 数据绑定)
 ├─ 普通参数 (如 @RequestParam) → 直接赋值
 ├─ Model/Map → 传递隐含模型
 └─ @RequestBody → 【消息转换器】反序列化请求体
 ↓
【执行目标方法】 Controller 中的业务逻辑
 ↓
【处理返回值】
 ├─ @ResponseBody / RestController → 【消息转换器】序列化返回值
 └─ 返回页面路径 → 【ViewResolver】 视图解析,Model 数据合并
 ↓
返回响应
1
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对象再到响应数据的完整映射过程。

最近更新: 9/19/2026, 1:27:08 PM
编程NOTE   |