每篇一句
在絕對力量面前,一切技巧都是浮云
前言
上文 介紹了Http內(nèi)容協(xié)商的一些概念,以及Spring MVC內(nèi)置的4種協(xié)商方式使用介紹。本文主要針對Spring MVC內(nèi)容協(xié)商方式:從步驟、原理層面理解,最后達(dá)到通過自己來擴(kuò)展協(xié)商方式效果。
首先肯定需要介紹的,那必然就是Spring MVC的默認(rèn)支持的四大協(xié)商策略的原理分析嘍:
ContentNegotiationStrategy
該接口就是Spring MVC實現(xiàn)內(nèi)容協(xié)商的策略接口:
// 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 參數(shù)排序,其次按照 quality 參數(shù)排序
// 如果請求的媒體類型不能被解析則拋出 HttpMediaTypeNotAcceptableException 異常
List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException;
}
說白了,這個策略接口就是想知道客戶端的請求需要什么類型(MediaType)的數(shù)據(jù)List。從 上文 我們知道Spring MVC它支持了4種不同的協(xié)商機(jī)制,它都和此策略接口相關(guān)的。
它的繼承樹:

從實現(xiàn)類的名字上就能看出它和上文提到的4種方式恰好是一一對應(yīng)著的(
ContentNegotiationManager除外)。
Spring MVC默認(rèn)加載兩個該策略接口的實現(xiàn)類:
ServletPathExtensionContentNegotiationStrategy-->根據(jù)文件擴(kuò)展名(支持RESTful)。
HeaderContentNegotiationStrategy-->根據(jù)HTTP Header里的Accept字段(支持Http)。
HeaderContentNegotiationStrategy
Accept Header解析:它根據(jù)請求頭Accept來協(xié)商。
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,則默認(rèn)使用MediaType.ALL 也就是*/*
AbstractMappingContentNegotiationStrategy
通過file extension/query param來協(xié)商的抽象實現(xiàn)類。在了解它之前,有必要先插隊先了解MediaTypeFileExtensionResolver它的作用:
MediaTypeFileExtensionResolver:MediaType和路徑擴(kuò)展名解析策略的接口,例如將 .json 解析成 application/json 或者反向解析
// @since 3.2
public interface MediaTypeFileExtensionResolver {
// 根據(jù)指定的mediaType返回一組文件擴(kuò)展名
List<String> resolveFileExtensions(MediaType mediaType);
// 返回該接口注冊進(jìn)來的所有的擴(kuò)展名
List<String> getAllFileExtensions();
}
繼承樹如下:

顯然,本處只需要講解它的直接實現(xiàn)子類
MappingMediaTypeFileExtensionResolver即可:
MappingMediaTypeFileExtensionResolver
public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExtensionResolver {
// key是lowerCaseExtension,value是對應(yīng)的mediaType
private final ConcurrentMap<String, MediaType> mediaTypes = new ConcurrentHashMap<>(64);
// 和上面相反,key是mediaType,value是lowerCaseExtension(顯然用的是多值map)
private final MultiValueMap<MediaType, String> fileExtensions = new LinkedMultiValueMap<>();
// 所有的擴(kuò)展名(List非set哦~)
private final List<String> allFileExtensions = new ArrayList<>();
...
public Map<String, MediaType> getMediaTypes() {
return this.mediaTypes;
}
// protected 方法
protected List<MediaType> getAllMediaTypes() {
return new ArrayList<>(this.mediaTypes.values());
}
// 給extension添加一個對應(yīng)的mediaType
// 采用ConcurrentMap是為了避免出現(xiàn)并發(fā)情況下導(dǎo)致的一致性問題
protected void addMapping(String extension, MediaType mediaType) {
MediaType previous = this.mediaTypes.putIfAbsent(extension, mediaType);
if (previous == null) {
this.fileExtensions.add(mediaType, extension);
this.allFileExtensions.add(extension);
}
}
// 接口方法:拿到指定的mediaType對應(yīng)的擴(kuò)展名們~
@Override
public List<String> resolveFileExtensions(MediaType mediaType) {
List<String> fileExtensions = this.fileExtensions.get(mediaType);
return (fileExtensions != null ? fileExtensions : Collections.emptyList());
}
@Override
public List<String> getAllFileExtensions() {
return Collections.unmodifiableList(this.allFileExtensions);
}
// protected 方法:根據(jù)擴(kuò)展名找到一個MediaType~(當(dāng)然可能是找不到的)
@Nullable
protected MediaType lookupMediaType(String extension) {
return this.mediaTypes.get(extension.toLowerCase(Locale.ENGLISH));
}
}
此抽象類維護(hù)一些Map以及提供操作的方法,它維護(hù)了一個文件擴(kuò)展名和MediaType的雙向查找表。擴(kuò)展名和MediaType的對應(yīng)關(guān)系:
- 一個
MediaType對應(yīng)N個擴(kuò)展名 - 一個擴(kuò)展名最多只會屬于一個MediaType~
繼續(xù)回到AbstractMappingContentNegotiationStrategy。
// @since 3.2 它是個協(xié)商策略抽象實現(xiàn),同時也有了擴(kuò)展名+MediaType對應(yīng)關(guān)系的能力
public abstract class AbstractMappingContentNegotiationStrategy extends MappingMediaTypeFileExtensionResolver implements ContentNegotiationStrategy {
// Whether to only use the registered mappings to look up file extensions,
// or also to use dynamic resolution (e.g. via {@link MediaTypeFactory}.
// org.springframework.http.MediaTypeFactory是Spring5.0提供的一個工廠類
// 它會讀取/org/springframework/http/mime.types這個文件,里面有記錄著對應(yīng)關(guān)系
private boolean useRegisteredExtensionsOnly = false;
// Whether to ignore requests with unknown file extension. Setting this to
// 默認(rèn)false:若認(rèn)識不認(rèn)識的擴(kuò)展名,拋出異常:HttpMediaTypeNotAcceptableException
private boolean ignoreUnknownExtensions = false;
// 唯一構(gòu)造函數(shù)
public AbstractMappingContentNegotiationStrategy(@Nullable Map<String, MediaType> mediaTypes) {
super(mediaTypes);
}
// 實現(xiàn)策略接口方法
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
// getMediaTypeKey:抽象方法(讓子類把擴(kuò)展名這個key提供出來)
return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));
}
public List<MediaType> resolveMediaTypeKey(NativeWebRequest webRequest, @Nullable String key) throws HttpMediaTypeNotAcceptableException {
if (StringUtils.hasText(key)) {
// 調(diào)用父類方法:根據(jù)key去查找出一個MediaType出來
MediaType mediaType = lookupMediaType(key);
// 找到了就return就成(handleMatch是protected的空方法~~~ 子類目前沒有實現(xiàn)的)
if (mediaType != null) {
handleMatch(key, mediaType); // 回調(diào)
return Collections.singletonList(mediaType);
}
// 若沒有對應(yīng)的MediaType,交給handleNoMatch處理(默認(rèn)是拋出異常,見下面)
// 注意:handleNoMatch如果通過工廠找到了,那就addMapping()保存起來(相當(dāng)于注冊上去)
mediaType = handleNoMatch(webRequest, key);
if (mediaType != null) {
addMapping(key, mediaType);
return Collections.singletonList(mediaType);
}
}
return MEDIA_TYPE_ALL_LIST; // 默認(rèn)值:所有
}
// 此方法子類ServletPathExtensionContentNegotiationStrategy有復(fù)寫
@Nullable
protected MediaType handleNoMatch(NativeWebRequest request, String key) throws HttpMediaTypeNotAcceptableException {
// 若不是僅僅從注冊里的拿,那就再去MediaTypeFactory里看看~~~ 找到了就返回
if (!isUseRegisteredExtensionsOnly()) {
Optional<MediaType> mediaType = MediaTypeFactory.getMediaType("file." + key);
if (mediaType.isPresent()) {
return mediaType.get();
}
}
// 忽略找不到,返回null吧 否則拋出異常:HttpMediaTypeNotAcceptableException
if (isIgnoreUnknownExtensions()) {
return null;
}
throw new HttpMediaTypeNotAcceptableException(getAllMediaTypes());
}
}
該抽象類實現(xiàn)了模版處理流程。
由子類去決定:你的擴(kuò)展名是來自于URL的參數(shù)還是來自于path...
ParameterContentNegotiationStrategy
上面抽象類的子類具體實現(xiàn),從名字中能看出擴(kuò)展名來自于param參數(shù)。
public class ParameterContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
// 請求參數(shù)默認(rèn)的key是format,你是可以設(shè)置和更改的。(set方法)
private String parameterName = "format";
// 唯一構(gòu)造
public ParameterContentNegotiationStrategy(Map<String, MediaType> mediaTypes) {
super(mediaTypes);
}
... // 生路get/set
// 小Tips:這里調(diào)用的是getParameterName()而不是直接用屬性名,以后建議大家設(shè)計框架也都這么使用 雖然很多時候效果是一樣的,但更符合使用規(guī)范
@Override
@Nullable
protected String getMediaTypeKey(NativeWebRequest request) {
return request.getParameter(getParameterName());
}
}
根據(jù)一個查詢參數(shù)(query parameter)判斷請求的MediaType,該查詢參數(shù)缺省使用format。
需要注意的是:基于param的此策略
Spring MVC雖然支持,但默認(rèn)是木有開啟的,若想使用需要手動顯示開啟
PathExtensionContentNegotiationStrategy
它的擴(kuò)展名需要從Path里面分析出來。
public class PathExtensionContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
private UrlPathHelper urlPathHelper = new UrlPathHelper();
// 它額外提供了一個空構(gòu)造
public PathExtensionContentNegotiationStrategy() {
this(null);
}
// 有參構(gòu)造
public PathExtensionContentNegotiationStrategy(@Nullable Map<String, MediaType> mediaTypes) {
super(mediaTypes);
setUseRegisteredExtensionsOnly(false);
setIgnoreUnknownExtensions(true); // 注意:這個值設(shè)置為了true
this.urlPathHelper.setUrlDecode(false); // 不需要解碼(url請勿有中文)
}
// @since 4.2.8 可見Spring MVC允許你自己定義解析的邏輯
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
this.urlPathHelper = urlPathHelper;
}
@Override
@Nullable
protected String getMediaTypeKey(NativeWebRequest webRequest) {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request == null) {
return null;
}
// 借助urlPathHelper、UriUtils從URL中把擴(kuò)展名解析出來
String path = this.urlPathHelper.getLookupPathForRequest(request);
String extension = UriUtils.extractFileExtension(path);
return (StringUtils.hasText(extension) ? extension.toLowerCase(Locale.ENGLISH) : null);
}
// 子類ServletPathExtensionContentNegotiationStrategy有使用和復(fù)寫
// 它的作用是面向Resource找到這個資源對應(yīng)的MediaType ~
@Nullable
public MediaType getMediaTypeForResource(Resource resource) { ... }
}
根據(jù)請求URL路徑中所請求的文件資源的擴(kuò)展名部分判斷請求的MediaType(借助UrlPathHelper和UriUtils解析URL)。
ServletPathExtensionContentNegotiationStrategy
它是對PathExtensionContentNegotiationStrategy的擴(kuò)展,和Servlet容器有關(guān)了。因為Servlet額外提供了這個方法:ServletContext#getMimeType(String)來處理文件的擴(kuò)展名問題。
public class ServletPathExtensionContentNegotiationStrategy extends PathExtensionContentNegotiationStrategy {
private final ServletContext servletContext;
... // 省略構(gòu)造函數(shù)
// 一句話:在去工廠找之前,先去this.servletContext.getMimeType("file." + extension)這里找一下,找到就直接返回。否則再進(jìn)工廠
@Override
@Nullable
protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) throws HttpMediaTypeNotAcceptableException { ... }
// 一樣的:先this.servletContext.getMimeType(resource.getFilename()) 再交給父類處理
@Override
public MediaType getMediaTypeForResource(Resource resource) { ... }
// 兩者調(diào)用父類的條件都是:mediaType == null || MediaType.APPLICATION_OCTET_STREAM.equals(mediaType)
}
說明:ServletPathExtensionContentNegotiationStrategy是Spring MVC默認(rèn)就開啟支持的策略,無需手動開啟。
FixedContentNegotiationStrategy
固定類型解析:返回固定的MediaType。
public class FixedContentNegotiationStrategy implements ContentNegotiationStrategy {
private final List<MediaType> contentTypes;
// 構(gòu)造函數(shù):必須指定MediaType
// 一般通過@RequestMapping.produces這個注解屬性指定(可指定多個)
public FixedContentNegotiationStrategy(MediaType contentType) {
this(Collections.singletonList(contentType));
}
// @since 5.0
public FixedContentNegotiationStrategy(List<MediaType> contentTypes) {
this.contentTypes = Collections.unmodifiableList(contentTypes);
}
}
固定參數(shù)類型非常簡單,構(gòu)造函數(shù)傳進(jìn)來啥返回啥(不能為null)。
==ContentNegotiationManager==
介紹完了上面4中協(xié)商策略,開始介紹這個協(xié)商"容器"。
這個管理器它的作用特別像之前講述的xxxComposite這種“容器”管理類,總體思想是管理、委托,有了之前的基礎(chǔ)了解起他還是非常簡單的了。
// 它不僅管理一堆strategies(List),還管理一堆resolvers(Set)
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
private final List<ContentNegotiationStrategy> strategies = new ArrayList<>();
private final Set<MediaTypeFileExtensionResolver> resolvers = new LinkedHashSet<>();
...
// 若沒特殊指定,至少是包含了這一種的策略的:HeaderContentNegotiationStrategy
public ContentNegotiationManager() {
this(new HeaderContentNegotiationStrategy());
}
... // 因為比較簡單,所以省略其它代碼
}
它是一個ContentNegotiationStrategy容器,同時也是一個MediaTypeFileExtensionResolver容器。自身同時實現(xiàn)了這兩個接口。
ContentNegotiationManagerFactoryBean
顧名思義,它是專門用于來創(chuàng)建一個ContentNegotiationManager的FactoryBean。
// @since 3.2 還實現(xiàn)了ServletContextAware,可以得到當(dāng)前servlet容器上下文
public class ContentNegotiationManagerFactoryBean implements FactoryBean<ContentNegotiationManager>, ServletContextAware, InitializingBean {
// 默認(rèn)就是開啟了對后綴的支持的
private boolean favorPathExtension = true;
// 默認(rèn)沒有開啟對param的支持
private boolean favorParameter = false;
// 默認(rèn)也是開啟了對Accept的支持的
private boolean ignoreAcceptHeader = false;
private Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>();
private boolean ignoreUnknownPathExtensions = true;
// Jaf是一個數(shù)據(jù)處理框架,可忽略
private Boolean useJaf;
private String parameterName = "format";
private ContentNegotiationStrategy defaultNegotiationStrategy;
private ContentNegotiationManager contentNegotiationManager;
private ServletContext servletContext;
... // 省略普通的get/set
// 注意這里傳入的是:Properties 表示后綴和MediaType的對應(yīng)關(guān)系
public void setMediaTypes(Properties mediaTypes) {
if (!CollectionUtils.isEmpty(mediaTypes)) {
for (Entry<Object, Object> entry : mediaTypes.entrySet()) {
String extension = ((String)entry.getKey()).toLowerCase(Locale.ENGLISH);
MediaType mediaType = MediaType.valueOf((String) entry.getValue());
this.mediaTypes.put(extension, mediaType);
}
}
}
public void addMediaType(String fileExtension, MediaType mediaType) {
this.mediaTypes.put(fileExtension, mediaType);
}
...
// 這里面處理了很多默認(rèn)邏輯
@Override
public void afterPropertiesSet() {
List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>();
// 默認(rèn)favorPathExtension=true,所以是支持path后綴模式的
// servlet環(huán)境使用的是ServletPathExtensionContentNegotiationStrategy,否則使用的是PathExtensionContentNegotiationStrategy
//
if (this.favorPathExtension) {
PathExtensionContentNegotiationStrategy strategy;
if (this.servletContext != null && !isUseJafTurnedOff()) {
strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes);
} else {
strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
}
strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
if (this.useJaf != null) {
strategy.setUseJaf(this.useJaf);
}
strategies.add(strategy);
}
// 默認(rèn)favorParameter=false 木有開啟滴
if (this.favorParameter) {
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
strategy.setParameterName(this.parameterName);
strategies.add(strategy);
}
// 注意這前面有個!,所以默認(rèn)Accept也是支持的
if (!this.ignoreAcceptHeader) {
strategies.add(new HeaderContentNegotiationStrategy());
}
// 若你喜歡,你可以設(shè)置一個defaultNegotiationStrategy 最終也會被add進(jìn)去
if (this.defaultNegotiationStrategy != null) {
strategies.add(this.defaultNegotiationStrategy);
}
// 這部分我需要提醒注意的是:這里使用的是ArrayList,所以你add的順序就是u最后的執(zhí)行順序
// 所以若你指定了defaultNegotiationStrategy,它也是放到最后的
this.contentNegotiationManager = new ContentNegotiationManager(strategies);
}
// 三個接口方法
@Override
public ContentNegotiationManager getObject() {
return this.contentNegotiationManager;
}
@Override
public Class<?> getObjectType() {
return ContentNegotiationManager.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
這里解釋了 該文 的順序(后綴 > 請求參數(shù) > HTTP首部Accept)現(xiàn)象。Spring MVC是通過它來創(chuàng)建ContentNegotiationManager進(jìn)而管理協(xié)商策略的。
內(nèi)容協(xié)商的配置:ContentNegotiationConfigurer
雖然說默認(rèn)情況下Spring開啟的協(xié)商支持能覆蓋我們絕大部分應(yīng)用場景了,但不乏有的時候我們也還是需要對它進(jìn)行個性化的,那么這部分就講解下對它的個性化配置~
ContentNegotiationConfigurer
它用于"收集"配置項,根據(jù)你提供的配置項來創(chuàng)建出一個ContentNegotiationManager。
public class ContentNegotiationConfigurer {
private final ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean();
private final Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>();
public ContentNegotiationConfigurer(@Nullable ServletContext servletContext) {
if (servletContext != null) {
this.factory.setServletContext(servletContext);
}
}
// @since 5.0
public void strategies(@Nullable List<ContentNegotiationStrategy> strategies) {
this.factory.setStrategies(strategies);
}
...
public ContentNegotiationConfigurer defaultContentTypeStrategy(ContentNegotiationStrategy defaultStrategy) {
this.factory.setDefaultContentTypeStrategy(defaultStrategy);
return this;
}
// 手動創(chuàng)建出一個ContentNegotiationManager 此方法是protected
// 唯一調(diào)用處是:WebMvcConfigurationSupport
protected ContentNegotiationManager buildContentNegotiationManager() {
this.factory.addMediaTypes(this.mediaTypes);
return this.factory.build();
}
}
ContentNegotiationConfigurer可以認(rèn)為是提供一個設(shè)置ContentNegotiationManagerFactoryBean的入口(自己內(nèi)容new了一個它的實例),最終交給WebMvcConfigurationSupport向容器內(nèi)注冊這個Bean:
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
...
// 請注意是BeanName為:mvcContentNegotiationManager
// 若實在有需要,你是可以覆蓋的~~~~
@Bean
public ContentNegotiationManager mvcContentNegotiationManager() {
if (this.contentNegotiationManager == null) {
ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer(this.servletContext);
configurer.mediaTypes(getDefaultMediaTypes()); // 服務(wù)端默認(rèn)支持的后綴名-->MediaType們~~~
// 這個方法就是回調(diào)我們自定義配置的protected方法~~~~
configureContentNegotiation(configurer);
// 調(diào)用方法生成一個管理器
this.contentNegotiationManager = configurer.buildContentNegotiationManager();
}
return this.contentNegotiationManager;
}
// 默認(rèn)支持的協(xié)商MediaType們~~~~
protected Map<String, MediaType> getDefaultMediaTypes() {
Map<String, MediaType> map = new HashMap<>(4);
// 幾乎不用
if (romePresent) {
map.put("atom", MediaType.APPLICATION_ATOM_XML);
map.put("rss", MediaType.APPLICATION_RSS_XML);
}
// 若導(dǎo)了jackson對xml支持的包,它就會被支持
if (jaxb2Present || jackson2XmlPresent) {
map.put("xml", MediaType.APPLICATION_XML);
}
// jackson.databind就支持json了,所以此處一般都是滿足的
// 額外還支持到了gson和jsonb。希望不久將來內(nèi)置支持fastjson
if (jackson2Present || gsonPresent || jsonbPresent) {
map.put("json", MediaType.APPLICATION_JSON);
}
if (jackson2SmilePresent) {
map.put("smile", MediaType.valueOf("application/x-jackson-smile"));
}
if (jackson2CborPresent) {
map.put("cbor", MediaType.valueOf("application/cbor"));
}
return map;
}
...
}
Tips:
WebMvcConfigurationSupport是@EnableWebMvc導(dǎo)進(jìn)去的。
配置實踐
有了上面理論的支撐,那么使用Spring MVC協(xié)商的最佳實踐配置可參考如下(大多數(shù)情況下都無需配置):
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorParameter(true)
//.parameterName("mediaType")
//.defaultContentTypeStrategy(new ...) // 自定義一個默認(rèn)的內(nèi)容協(xié)商策略
//.ignoreAcceptHeader(true) // 禁用Accept協(xié)商方式
//.defaultContentType(MediaType.APPLICATION_JSON) // 它的效果是new FixedContentNegotiationStrategy(contentTypes) 增加了對固定策略的支
//.strategies(list);
//.useRegisteredExtensionsOnly() //PathExtensionContentNegotiationStrategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
;
}
}
總結(jié)
本文從原理上分析了Spring MVC對內(nèi)容協(xié)商策略的管理、使用以及開放的配置,旨在做到心中有數(shù),從而更好、更安全、更方便的進(jìn)行擴(kuò)展,對下文內(nèi)容協(xié)商視圖的理解有非常大的幫助作用,有興趣的可持續(xù)關(guān)注~
相關(guān)閱讀
ContentNegotiation內(nèi)容協(xié)商機(jī)制(一)---Spring MVC內(nèi)置支持的4種內(nèi)容協(xié)商方式【享學(xué)Spring MVC】
ContentNegotiation內(nèi)容協(xié)商機(jī)制(二)---Spring MVC內(nèi)容協(xié)商實現(xiàn)原理及自定義配置【享學(xué)Spring MVC】
ContentNegotiation內(nèi)容協(xié)商機(jī)制(三)---在視圖View上的應(yīng)用:ContentNegotiatingViewResolver深度解析【享學(xué)Spring MVC】
知識交流
==The last:如果覺得本文對你有幫助,不妨點個贊唄。當(dāng)然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的~==
若對技術(shù)內(nèi)容感興趣可以加入wx群交流:Java高工、架構(gòu)師3群。
若群二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)。并且備注:"java入群" 字樣,會手動邀請入群
==若對Spring、SpringBoot、MyBatis等源碼分析感興趣,可加我wx:fsx641385712,手動邀請你入群一起飛==