Spring版本5.1.4.release.
内容协商是用在Springmvc返回Controller方法结果序列化时使用,而不是解析mvc参数时使用。
Springmvc支持4种内容协商,拓展名、固定值、Http的头部Accept、请求参数format,那Springmvc中怎么实现的呢,怎么使用已经有很多人分析了,这里来分析下怎么实现的。
RequestResponseBodyMethodProcessor#handleReturnValue中调用了父类的writeWithMessageConverters,AbstractMessageConverterMethodProcessor#writeWithMessageConverters()中调用了getAcceptableMediaTypes,如下List-1
List-1
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
throws HttpMediaTypeNotAcceptableException {
return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
}
List-2中,ContentNegotiationManager的resolveMediaTypes方法中循坏遍历ContentNegotiationStrategy,分别调用其resolveMediaTypes方法。
List-2
@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;
}
ContentNegotiationManager是由ContentNegotiationManagerFactoryBean创建的,如下图1所示,ContentNegotiationManagerFactoryBean实现了FactoryBean,通过getObject方法获取创建好后的ContentNegotiationManager,实现接口ServletContextAware是为了获取ServletContext从而构造ServletPathExtensionContentNegotiationStrategy,至于实现InitializingBean则是用afterPropertiesSet进行初始化ContentNegotiationManager
图1
List-3
public ContentNegotiationManager build() {
List<ContentNegotiationStrategy> strategies = new ArrayList<>();
if (this.strategies != null) {
strategies.addAll(this.strategies);
}
else {
//1
if (this.favorPathExtension) {
PathExtensionContentNegotiationStrategy strategy;
if (this.servletContext != null && !useRegisteredExtensionsOnly()) {
strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes);
}
else {
strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
}
strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
if (this.useRegisteredExtensionsOnly != null) {
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
}
strategies.add(strategy);
}
//2
if (this.favorParameter) {
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
strategy.setParameterName(this.parameterName);
if (this.useRegisteredExtensionsOnly != null) {
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
}
else {
strategy.setUseRegisteredExtensionsOnly(true); // backwards compatibility
}
strategies.add(strategy);
}
//3
if (!this.ignoreAcceptHeader) {
strategies.add(new HeaderContentNegotiationStrategy());
}
//4
if (this.defaultNegotiationStrategy != null) {
strategies.add(this.defaultNegotiationStrategy);
}
}
this.contentNegotiationManager = new ContentNegotiationManager(strategies);
return this.contentNegotiationManager;
}
如上List-3所示,
- favorPathExtension是true,构造PathExtensionContentNegotiationStrategy,并加到结果结合strategies中,默认ServletPathExtensionContentNegotiationStrategy是不会构造的,除非我们手动的设置
- favorParameter是false,如果我们设置为true后,会构造ParameterContentNegotiationStrategy,即我们设置的format=json会生效
- ignoreAcceptHeader是false,所以会把HeaderContentNegotiationStrategy加入到结果集合中,即Http头部的Accept
- 如果设置了defaultNegotiationStrategy,就会把我们添加的自定义Strategy加入到结果集合中
如下List-4是ContentNegotiationManager的resolveMediaTypes方法,顺序遍历strategy,如果resolveMediaTypes返回的值不等于MEDIA_TYPE_ALL_LIST,那么就直接返回,结合List-3中添加的顺序,这就是路径拓展第一生效,第二format固定值,第三Http头部的Accept
List-4
@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;
}
ContentNegotiationStrategy使用了策略模式,在HeaderContentNegotiationStrategy中,从Http头部拿到Accept值后,还对结果按权重进行排序,这是有对应的JSR规范描述这个权重的计算的