內容協商 Spring ContentNegotiation

在SpringMVC中,我們訪問一個RESTful @ReponseBody接口時,spring可以實現根據path extension來給出不同的響應格式,如:

// Json
curl http://localhost:8080/springautowired/foo.json
{"foo":"bar"}
// Xml
curl http://localhost:8080/springqutowired/foo.xml
<?xml version="1.0" encoding="UTF-8"?>
<root><foo>bar</foo></root>

這是spring內容協商的一種外在表現形式。

內容協商,ContentNegotiation 在spring中關注的核心問題是:你想要什麼樣格式的數據你想要什麼,可以理解爲,http請求響應時,服務器應該往http response body裏寫什麼格式的數據

分層概念

在SpringMVC的概念中,View指的是數據的展現形式,Model可以理解成領域數據模型或者載體,MVC的核心是分層,分層的目的是爲了解耦。就View與Model之間的解耦,指的是數據本身與數據展示形式的解耦,在spring應用內部,領域數據的載體,可以簡單理解爲bean。到了View層,數據的展現形式多種多樣,可以是Jsp, velocity,pdf 或者RESTful的json, xml等。

使用SpringMVC時,通常有兩種方式產生輸出:

  • 使用RESTful 的 @ResponseBody,藉助於HttpMessageConverter來輸出像Json Xml等類型的的數據

  • 使用 view resolution,可以生成更傳統的Html頁面(Jsp、Velocity等)

無論用哪種方式,你都有可能需要把Controller返回的相同的數據內容轉成不同的表現形式。

在之前的內容中有提到HttpMessageConverter 是spring處理rest請求時負責解析與轉換http消息的邏輯單元。藉助HttpMessageConverter,sping可以實現應用領域數據bean與http消息body的轉換。那麼問題來了,當前一個http請求完成時,該用哪個具體的HttpMessageConverter實現來回寫數據,這就是ContentNegotiation關注的內容。

協商策略

提到協商,必須有協商的點和策略,對應一個http請求,spring來判定請求中htto body格式主要通過以下三個因素(優先級同順序):

  • path extension  也稱爲path suffix 就是文章開頭提到的例子中的url後綴 .json .xml

  • url parameter 是一個明確指定媒體類型的參數,即通過顯式的參數告訴服務器,我想要什麼格式的數據。如format=json,  參數名可指定,默認是format

  • http header ( Accept) 如果上邊兩項都沒有,能參考http header Accept來判定響應數據格式,因某些瀏覽器或者有些http請求不完全按規則來指定需要的媒體類型,所以使用時需要謹慎使用header

上述協商的規則,在spring中被抽象爲接口:

// A strategy for resolving the requested media types 
// for a request.
ContentNegotiationStrategy

  • FixContentNegotiationStrategy一般用來處理默認數據格式

  • HeaderContentNegotiationStrategy用來處理http Accept header的方式

  • ParameterContentNegotiationStrategy用來處理顯式的媒體類型參數方式

  • PathExtensionContentNegotiationStrategy處理url後綴方式

上邊這些strategy實現類以組合模式的形式封裝成ContentNegotiationManager,對外提供邏輯接口。

ContentNegotiationManager的resolveMediaTypes方法如下:

  @Override
  public List<MediaType> resolveMediaTypes(NativeWebRequest request) 
  throws HttpMediaTypeNotAcceptableException {
    for (ContentNegotiationStrategy strategy : this.strategies) {
      List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
      if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
        continue;
      }
      return mediaTypes;
    }
    return Collections.emptyList();
  }

使用方式

在項目中,可以通過ContentNegotiationManagerFactoryBean來配置一個全局的內容協商contentNegotiationManager,給spring MVC使用,ContentNegotiationManagerFactoryBean中主要屬性如下:

public class ContentNegotiationManagerFactoryBean
        implements FactoryBean<ContentNegotiationManager>, 
        ServletContextAware, InitializingBean {
     //是否關注url路徑後綴
    private boolean favorPathExtension = true;
    //是否關注媒體參數
    private boolean favorParameter = false;
    //是否忽略http Accept header
    private boolean ignoreAcceptHeader = false;
    // url 後綴與媒體類型的映射
    private Map<String, MediaType> mediaTypes = 
    new HashMap<String, MediaType>();
    //忽略未知的後綴
    private boolean ignoreUnknownPathExtensions = true;
    // 默認的媒體參數名
    private String parameterName = "format”;

可以這樣定義一個contentNegotiationManager:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
  @Override
  public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    configurer.favorPathExtension(false).
            favorParameter(true).
            parameterName("mediaType").
            ignoreAcceptHeader(true)
            defaultContentType(MediaType.APPLICATION_JSON).
            mediaType("xml", MediaType.APPLICATION_XML).
            mediaType("json", MediaType.APPLICATION_JSON);
  }
}

xml形式的配置: 

 <bean id="contentNegotiationManager"
             class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="favorPathExtension" value="false" />
    <property name="favorParameter" value="true" />
    <property name="parameterName" value="mediaType" />
    <property name="ignoreAcceptHeader" value="true"/>
    <property name="defaultContentType" value="application/json" />
    <property name="mediaTypes">
        <map>
            <entry key="json" value="application/json" />
            <entry key="xml" value="application/xml" />
       </map>
    </property>
</bean>

ContentNegotiation不僅限於Rest風格的http請求,SpringMVC中還有ContentNegotiatingViewResolver完成了viewResolver部分的內容協商功能,有興趣的小夥伴可以查看部分的源碼。 

以上便是今天的全部內容,感謝關注,歡迎小夥伴留言反饋。

往期回顧:

SpringAutowired

長按,識別二維碼,加關注

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章