每篇一句
在绝对力量面前,一切技巧都是浮云
前言
上文 介绍了Http内容协商的一些概念,以及Spring MVC
内置的4种协商方式使用介绍。本文主要针对Spring MVC
内容协商方式:从步骤、原理层面理解,最后达到通过自己来扩展协商方式效果。
首先肯定需要介绍的,那必然就是Spring MVC
的默认支持的四大协商策略的原理分析喽:
ContentNegotiationStrategy
该接口就是Spring MVC
实现内容协商的策略接口:
// A strategy for resolving the requested media types for a request.
// @since 3.2
@FunctionalInterface
public interface ContentNegotiationStrategy {
// @since 5.0.5
List<MediaType> MEDIA_TYPE_ALL_LIST = Collections.singletonList(MediaType.ALL);
// 将给定的请求解析为媒体类型列表
// 返回的 List 首先按照 specificity 参数排序,其次按照 quality 参数排序
// 如果请求的媒体类型不能被解析则抛出 HttpMediaTypeNotAcceptableException 异常
List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException;
}
说白了,这个策略接口就是想知道客户端的请求需要什么类型(MediaType
)的数据List
。从 上文 我们知道Spring MVC
它支持了4种不同的协商机制,它都和此策略接口相关的。
它的继承树:
从实现类的名字上就能看出它和上文提到的4种方式恰好是一一对应着的(ContentNegotiationManager
除外)。
Spring MVC
默认加载两个该策略接口的实现类:
ServletPathExtensionContentNegotiationStrategy
-->根据文件扩展名(支持RESTful)。
HeaderContentNegotiationStrategy
-->根据HTTP Header
里的Accept
字段(支持Http)。
HeaderContentNegotiationStrategy
Accept Header
解析:它根据请求头Accept
来协商。
public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
// 我的Chrome浏览器值是:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3]
// postman的值是:[*/*]
String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
if (headerValueArray == null) {
return MEDIA_TYPE_ALL_LIST;
}
List<String> headerValues = Arrays.asList(headerValueArray);
try {
List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
// 排序
MediaType.sortBySpecificityAndQuality(mediaTypes);
// 最后Chrome浏览器的List如下:
// 0 = {MediaType@6205} "text/html"
// 1 = {MediaType@6206} "application/xhtml+xml"
// 2 = {MediaType@6207} "image/webp"
// 3 = {MediaType@6208} "image/apng"
// 4 = {MediaType@6209} "application/signed-exchange;v=b3"
// 5 = {MediaType@6210} "application/xml;q=0.9"
// 6 = {MediaType@6211} "*/*;q=0.8"
return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
} catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotAcceptableException("Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
}
}
}
可以看到,如果没有传递Accept,则默认使用MediaType.ALL 也就是*/*
AbstractMappingContentNegotiationStrategy
通过file extension
/query param
来协商的抽象实现类。在了解它之前,有必要先插队先了解MediaTypeFileExtensionResolver
它的作用:
---
MediaTypeFileExtensionResolver
:MediaType
和路径扩展名解析策略的接口,例如将 .json
解析成 application/json
或者反向解析
// @since 3.2
public interface MediaTypeFileExtensionResolver {
// 根据指定的mediaType返回一组文件扩展名
List<String> resolveFileExtensions(MediaType mediaType);
// 返回该接口注册进来的所有的扩展名
List<String> getAllFileExtensions();
}
继承树如下:
显然,本处只需要讲解它的直接实现子类MappingMediaTypeFileExtensionResolver
即可:
MappingMediaTypeFileExtensionResolver
public class MappingMediaTypeFileExtensionResolver i