Spring--視圖內(nèi)容協(xié)商(三)

Spring--視圖內(nèi)容協(xié)商(三)

本文是學(xué)習(xí)了小馬哥在慕課網(wǎng)的課程的《Spring Boot 2.0深度實踐之核心技術(shù)篇》的內(nèi)容結(jié)合自己的需要和理解做的筆記。

上兩篇文章,簡單介紹了一下Spring的視圖內(nèi)容協(xié)商。接下來我們針對 REST 內(nèi)容協(xié)商做一下介紹。

沒想到截圖效果怎么不好,實在是失落。

大綱

  • 理解REST請求媒體類型
  • REST內(nèi)容協(xié)商流程
  • REST內(nèi)容協(xié)商源碼分析

理解REST請求媒體類型

理解解析請求的媒體類型

在我們使用Spring開發(fā)的時候,相信 @RequestMapping 這個注解再也熟悉不過了,相信使用Restful API 接口形式開發(fā)的小伙伴們都避免不了設(shè)置API的媒體類型 比如 AcceptContent-Type ,在前一篇我們也說過 Spring 通過 ContentNegotiationManagerContentNegotiationStrategy 解析請求中的媒體類型。

HeaderContentNegotiationStrategy 為例

  • 如果解析成功,則返回合法的MediaType集合。
  • 否則,返回MediaType.ALL默認(rèn)的媒體類型,也就是 */*

在這里我們可以看一下HeaderContentNegotiationStrategy 源碼,具體解釋已經(jīng)在注釋中給出

@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
      throws HttpMediaTypeNotAcceptableException {
   //獲取 Accept的 媒體類型的字符串?dāng)?shù)組
   String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
   //如果為空 則返回 全部類型 也就是 */*
   if (headerValueArray == null) {
      return MEDIA_TYPE_ALL_LIST;
   }

   List<String> headerValues = Arrays.asList(headerValueArray);
   try {
      //轉(zhuǎn)換為Spring 內(nèi)置媒體類型集合 
      List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
       //最佳媒體類型排序
      MediaType.sortBySpecificityAndQuality(mediaTypes);
      return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
   }
   catch (InvalidMediaTypeException ex) {
      throw new HttpMediaTypeNotAcceptableException(
            "Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
   }
}

當(dāng)然 在 ContentNegotiationManager 中也會有判斷

@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
    //循環(huán)遍歷協(xié)商處理策略
   for (ContentNegotiationStrategy strategy : this.strategies) {
      //獲取媒體類型 比如 HeaderContentNegotiationStrategy 
      List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
       //如果媒體類型是 默認(rèn)的所有 即 */* 則跳過 
      if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
         continue;
      }
      return mediaTypes;
   }
   //如果都不滿足 則最后返回 默認(rèn)所有 即*/*
   return MEDIA_TYPE_ALL_LIST;
}

對比上面的源碼我們可以看到 Spring 做了很多層校驗 來判斷媒體類型是否為空。

理解可生成的媒體類型

使用@RequestMapping.produces() 屬性 來指定MediaType 類型集合 影響 瀏覽器響應(yīng)頭 Content-Type 媒體類型映射。

  • 如果 @RequestMapping.produces() 存在,返回指定 MediaType 列表。
  • 否則,返回已注冊的 HttpMessageConverter 列表中支持的 MediaType 列表。
  • 如果該列表與請求的媒體類型兼容,執(zhí)行第一個兼容 HttpMessageConverter 的實現(xiàn),默認(rèn)
    @RequestMapping#produces 內(nèi)容到響應(yīng)頭 Content-Type
    否則,拋出 HttpMediaTypeNotAcceptableException , HTTP Status Code : 415

這段源碼會在稍后的源碼分析中做詳解。

理解可消費的媒體類型

使用@RequestMapping#consumes 屬性,來設(shè)置兼容的MediaType 類型集合 過濾 請求頭 Content-Type 媒體類型映射。

  • 如果請求頭 Content-Type 媒體類型兼容 @RequestMapping.consumes() 屬性,執(zhí)行該 HandlerMethod
  • 否則 HandlerMethod 不會被調(diào)用。

這段源碼會在稍后的源碼分析中做詳解。

REST內(nèi)容協(xié)商流程

在講述REST協(xié)商流程之前,我們先來了解一下 對于REST內(nèi)容協(xié)商非常關(guān)鍵的兩個解析器

  • 處理方法參數(shù)解析器(HandlerMethodArgumentResolver
    • 用于 HTTP 請求中解析 HandlerMethod 參數(shù)內(nèi)容
  • 處理方法返回值解析器(HandlerMethodReturnValueHandler)
    • 用于 HandlerMethod 返回值解析為 HTTP 響應(yīng)內(nèi)容
流程圖

對于協(xié)商流程,在這里貼一張圖,流程大體就可以理解了,在結(jié)合下面的源碼分析,相信很快就可以明白Spring的視圖協(xié)商流程。

總體流程圖.PNG

針對 上述第10步 轉(zhuǎn)化HTTP消息 的詳細(xì)流程圖

詳細(xì)流程圖.PNG
調(diào)試前的代碼準(zhǔn)備

在這里需要增加一個簡單的User對象以及對應(yīng)請求的Controller

User.java

/**
 * 用戶對象
 */
public class User {

    private Long id;

    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

UserRestController.java

@RestController
public class UserRestController {


    //最終相應(yīng)回瀏覽器的Content-Type 是 produces 的內(nèi)容
    @PostMapping(value = "/user",
            consumes = "application/json;charset=UTF-8",
            produces = "application/json;charset=GBK")
    public User user(@RequestBody User user) {
        return user;
    }
}

添加完這兩個類之后,讓我們啟動一下Spring-boot項目。

我們可以對比上面的總體流程圖來對源碼進(jìn)行解讀。我們使用PostMan來發(fā)送一個簡單的請求。

postman1.PNG
postman2.PNG
首先我們先打開DispatcherServlet#doDispatch 方法。我們來關(guān)注下面兩個方法
st2.png
首先我們先來看一下 步驟 2和3的對應(yīng)方法
st1.png
接下來讓我們進(jìn)入到 步驟 4的對應(yīng)方法中
st3.png
接下來就是我們的重頭戲就是調(diào)用HandlerMethod,在這個階段就包括了剩下的步驟,也就是步驟5~10。
st4.png
我們一步一步的往方法里進(jìn)。
st5.png
我們接著進(jìn)入到 RequestMappingHanlderAdapter 中的 handleInternal 方法。
st7.png
st8.png
st9.png
我們接著進(jìn)入到 invokeAndHandle 方法
st10.png
st11.png
我們詳細(xì)看一下如何解析方法參數(shù)的。
st12.png
這里我們重點看一下 argumentResolvers.supportsParameter(parameter) 這段代碼 主要是判斷 參數(shù)處理器是否支持解析傳入的參數(shù)。
st13.png
判斷完某個解析器(RequestResponseBodyMethodProcessor)是否支持解析,接下來就是具體解析的操作了。
st14.png
進(jìn)入到 resolveArgument方法
st16.png
st15.png
在這里我們已經(jīng)獲取到了 入?yún)⒌闹?,我們回到最初調(diào)用的方法然后反射調(diào)用方法。
st17.png
st18.png
st19.png
至此 我們步驟8之前的已經(jīng)講解完了。下面我們講解一下返回值解析。我們重新回到org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle 方法中。
st20.png
進(jìn)入handleReturnValue 方法。
st21.png
我們來看一下 selectHandler() 這個方法
st22.png
選擇好了返回值解析器 (RequestResponseBodyMethodProcesser),下面我們就看看如何解析返回值的。
細(xì)心的小伙伴可以發(fā)現(xiàn) 這個解析器即是 方法參數(shù)解析器 又是 返回值解析器。這里就不多做解釋了 這是因為他即繼承AbstractMessageConverterMethodArgumentResolver 抽象類又實現(xiàn)了 HandlerMethodReturnValueHandler 接口。我們再進(jìn)入到里面一層的handleReturnValue
st23.png
st24.png
接下來的內(nèi)容就是詳細(xì)流程圖中的內(nèi)容了我們從第7步開始看,我們通過媒體類型來匹配HttpMessageConverter。
st25.png
st26.png
選擇好轉(zhuǎn)換器之后我們就可以進(jìn)行轉(zhuǎn)換了,我們看一下MappingJackson2HttpMessageConverter 是如何轉(zhuǎn)換然后返回相應(yīng)的。
st27.png
st28.png
st29.png
st30.png
最后,在PostMan中顯示返回結(jié)果
st31.png
st32.png
通過響應(yīng)頭 我們也可以看到 @RequestMapping.produces() 的作用。

總結(jié)

雖然協(xié)商邏輯以及流程比較繁瑣,但是在我們使用Spring的時候,這些功能給了我們很大的便利,至于 最后Jackson是如何序列化的,這里就不詳細(xì)說明了。不屬于本次內(nèi)容的范疇。 別的不多說,繼續(xù)努力吧。

最后編輯于
?著作權(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)容