Spring MVC內(nèi)容協(xié)商實現(xiàn)原理及自定義配置【享學(xué)Spring MVC】

每篇一句

在絕對力量面前,一切技巧都是浮云

前言

上文 介紹了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它的作用:



MediaTypeFileExtensionResolverMediaType和路徑擴(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)系:

  1. 一個MediaType對應(yīng)N個擴(kuò)展名
  2. 一個擴(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(借助UrlPathHelperUriUtils解析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)
}

說明:ServletPathExtensionContentNegotiationStrategySpring 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)建一個ContentNegotiationManagerFactoryBean

// @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,手動邀請你入群一起飛==

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容