基于请求参数的内容原理
# 300.基于请求参数的内容原理
上一篇博客我们介绍了内容协商的原理,重点是通过获取客户端(浏览器,PostMan 等)支持的媒体类型,也就是通过 HTTP 请求头的 accept 字段;
# 回顾
在介绍本节课的内容之前,先简单回顾下内容是如何被协商的。
首先是获取浏览器支持的媒体类型(第 3 行):
else {
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
//......
}
2
3
4
5
6
ps:位于
AbstractMessageConverterMethodProcessor
类第 215 行
然后该方法会调用 contentNegotiationManage(内容协商管理器)r 的方法:
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
}
2
3
这个管理器中有一个叫做 strategies 的 List,也就是存储了获取媒体类型的策略,其中有一个策略叫 HeaderContentNegotiationStrategy
,也就是基于 HTTP 请求头的策略;也是默认的策略
然后就会遍历所有策略(目前只有一个),并将媒体类型存放到结果中。
@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
由于我们目前的策略是基于请求头的,因此会调用 request 原生的获取请求头的 API,来获取媒体类型,也就是获取 ACCEPT 字段:
策略是一个接口,有几个实现类:
# 问题
在使用浏览器的时候,默认会带上 HTTP 请求头,是我们改变不了的;除非我们改成发送 Ajax 请求,设置请求头,或者使用 Postman 等工具,直接编辑请求头。
为了方便内容协商,SpringMVC 也提供了一个功能,让我们在请求参数中带上媒体类型!这就方便了内容协商。
由于该功能默认是关闭的,我们需要配置
# 开启参数方式内容协商
修改 application.yml,添加第 10~11 行:
spring:
resources:
static-locations: classpath:/haha/
mvc:
hiddenmethod:
filter:
enabled: true
contentnegotiation:
favor-parameter: true
2
3
4
5
6
7
8
9
我们可以按住 Ctrl,点进 favor-parameter,看其源码,可以看到是一个 set 方法:
public void setFavorParameter(boolean favorParameter) {
this.favorParameter = favorParameter;
}
2
3
然后我们看到定义该变量的地方,有注释
/**
* Whether a request parameter ("format" by default) should be used to determine
* the requested media type.
*/
private boolean favorParameter = false;
2
3
4
5
注释大意:可以通过一个请求参数(默认是 format)来告诉服务器用什么媒体类型
然后我们重启项目,访问时带上媒体类型,就能返回不同的媒体类型:
# 原理
接下来我们以 debug 的方式重启,来看其源码。我们直接运行到内容协商的部分:
debug 进去,之前我们的 strategies
列表只有一个参数,现在多了一个 ParameterContentNegotiationStrategy
,也就是基于请求参数的协商策略,并且参数名是 format
:
然后会遍历所有策略:默认是从第一个开始,也就是基于参数的策略
@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
我们 debug 进去:
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));
}
2
3
4
getMediaTypeKey 方法,则是获取我们参数的 key,我们步入:
public class ParameterContentNegotiationStrategy
extends AbstractMappingContentNegotiationStrategy {
private String parameterName = "format";
//.....
public String getParameterName() {
return this.parameterName;
}
@Override
@Nullable
protected String getMediaTypeKey(NativeWebRequest request) {
return request.getParameter(getParameterName());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
代码分析:
- 首先来到
ParameterContentNegotiationStrategy
类的getMediaTypeKey
方法,其调用 Servlet 原生的 API,获取请求参数 getParameterName
方法则返回的是成员变量parameterName
的值,也就是 format
接下来我们步入 resolveMediaTypeKey
方法:其会将参数(也就是 JSON)包装下,然后返回
然后 for 循环就结束了,返回媒体类型 JSON:
因此,最后就返回了 JSON 数据给浏览器。