内容协商原理
# 290.内容协商原理
上一节课主要是讲了 Converter 的原理,在寻找 Converter 的过程中,有一个很重要的地方就是内容协商,查找哪个 Converter 能处理数据;
接下来就讲讲内容协商的原理,以返回 XML 数据为例
# 引入依赖
引入 XML 的依赖:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2
3
4
# 测试
此时我们重启项目,并访问 localhost: 8888/test/person (opens new window),效果如下:
为什么会这样子?因为我们发送请求的时候,默认 XML 的权重比较高:
因此返回的也是 XML:
接下来我们就简单看看源码,看看其是如何做到内容协商的(有时候返回 JSON,有时候则是 XML)
# debug 方式启动
同上一篇博客,我们以 debug 方式启动,并步入到 ServletInvocableHandlerMethod
类中,并执行完了处理器方法,有了返回值:
然后我们调用 handler 的方法,我们步入进去:
再步入最后一行的 writeWithMessageConverters
方法:
@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
writeWithMessageConverters
方法之前的的代码暂且不表,我们直接来到媒体类型的判断(在源码 205 行):
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;
}
2
3
4
5
6
7
8
9
代码分析:
- 首先定义一个媒体类型,这个就是我们后续要确定的媒体类型(selectedMediaType)
- 然后获取响应头里的响应类型(contentType)。在之前的处理请求的过程中,可能有设置了响应类型,因此先获取下看看有没有,如果有则直接返回媒体类型
接下来来到关键的环节,else 分支代码如下:
else {
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
//......
}
2
3
4
5
6
代码分析:首先获取客户端可接受的参数类型,并放到 acceptableTypes 中。也就是 HTTP 请求中,发送的 accept 请求头字段。具体怎么获取呢?我们 debug 进 getAcceptableMediaTypes
方法:
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
}
2
3
ps:contentNegotiationManager 是内容协商管理器的意思
可以看到就一行,调用了 resolveMediaTypes 方法。我们再 debug 进去:
@Override
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;
}
2
3
4
5
6
7
8
9
10
11
这里 ContentNegotiationStrategy
是策略的意思,也就是我们获取媒体类型的策略,其实策略是不止一个的:
- 可以从 HTTP 请求头中的 accept 字段中获取
- 下一篇博客,我们会讲如何在请求参数中传递媒体类型,然后通过该类型来决定返回数据的媒体类型
最后我们 debug 进 resolveMediaTypes
方法(第 4 行),其实底层用的是 Servlet 的原生 API:获取后转为 List 集合,返回。
接下来就是获取到服务器能返回的数据类型:
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
接下来我们 debug 进 getProducibleMediaTypes
,其关键代码如下:
List<MediaType> result = new ArrayList<>();
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());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
也就是遍历所有 converter
,看看哪个 converter
能转换这个数据(调用其 canWrite
方法来判断),能的话就将该媒体类型放到一个 result 列表中
得到浏览器能处理的媒体类型,以及服务器能处理的媒体类型,接下来就是取出这个两个集合中,重复的部分,这个部分就是要返回的媒体类型了,就是通过一个双重循环;
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
2
3
4
5
6
7
8
然后会将能返回的数据类型排个序:
找到要返回的数据类型后,还有一次 for 循环,用来寻找哪个 converter 处理该种数据类型,找到后调用 write 方法来转换数据:
然后 write 方法里面,就是调用 converter 的底层方法了,例如调用 Jackson 框架的功能转换。
- 01
- 中国网络防火长城简史 转载10-12
- 03
- 公告:博客近期 RSS 相关问题10-02