使用Spring MVC創建 REST API


 

1.REST的基礎知識

當談論REST時,有一種常見的錯誤就是將其視為“基於URL的Web服務”——將REST作為另一種類型的遠程過程調用(remote procedurecall,RPC)機制,就像SOAP一樣,只不過是通過簡單的HTTP URL來觸發,而不是使用SOAP大量的XML命名空間。恰好相反,REST與RPC幾乎沒有任何關系。RPC是面向服務的,並關注於行為和動作;而REST是面向資源的,強調描述應用程序的事物和名詞。為了理解REST是什么,我們將它的首字母縮寫拆分為不同的構成部分:

 

表述性(Representational):REST資源實際上可以用各種形式來進行表述,包括XML、JSON(JavaScript Object Notation)甚至HTML——最適合資源使用者的任意形式;

狀態(State):當使用REST的時候,我們更關注資源的狀態而不是對資源采取的行為;

轉移(Transfer):REST涉及到轉移資源數據,它以某種表述性形式從一個應用轉移到另一個應用。更簡潔地講,REST就是將資源的狀態以最適合客戶端或服務端的形式從服務器端轉移到客戶端(或者反過來)。在REST中,資源通過URL進行識別和定位。至於RESTful URL的結構並沒有嚴格的規則,但是URL應該能夠識別資源,而不是簡單的發一條命令到服務器上。再次強調,關注的核心是事物,而不是行為

REST中會有行為,它們是通過HTTP方法來定義的。具體來講,也就是GET、POST、PUT、DELETE、PATCH以及其他的HTTP方法構成了

REST中的動作。這些HTTP方法通常會匹配為如下的CRUD動作:

Create:POSTRead:GET

Update:PUT或PATCH

Delete:DELETE

 

 

盡管通常來講,HTTP方法會映射為CRUD動作,但這並不是嚴格的限制。有時候,PUT可以用來創建新資源,POST可以用來更新資源。實際上,POST請求非冪等性(non-idempotent)的特點使其成為一個非常靈活的方法,對於無法適應其他HTTP方法語義的操作,它都能夠勝任。

 

2.Spring是如何支持REST的

當前的4.0版本中,Spring支持以下方式來創建REST資源:

控制器可以處理所有的HTTP方法,包含四個主要的REST方法:GET、PUT、DELETE以及POST。Spring 3.2及以上版本還支持PATCH方法;

借助@PathVariable注解,控制器能夠處理參數化的URL(將變量輸入作為URL的一部分);借助Spring的視圖和視圖解析器,資源能夠以多種方式進行表述,包括將模型數據渲染為XML、JSON、Atom以及RSS的View實現;可以使用ContentNegotiatingViewResolver來選擇最適合客戶端的表述;借助@ResponseBody注解和各種HttpMethodConverter實現,能夠替換基於視圖的渲染方式;類似地,@RequestBody注解以及HttpMethodConverter實現可以將傳入的HTTP數據轉化為傳入控制器處理方法的Java對象;借助RestTemplate,Spring應用能夠方便地使用REST資源。

 

3.創建第一個REST端點

首先,我們會在名為SpittleApiController的新控制器中創建第一個REST端點。如下的程序清單展現了這個新REST控制器起始的樣子,它會提供Spittle資源。這是一個很簡單的開始,但是在本章中,隨着不斷學習Spring REST編程模型的細節,我們將會不斷構建這個控制器

 

  對於非人類用戶的使用者,比如其他的應用或調用REST端點的代碼,資源表述的首選應該是XML和JSON。借助Spring同時支持這兩種方案非常簡單,所以沒有必要做一個非此即彼的選擇。按照我的意見,我推薦至少要支持JSON。JSON使用起來至少會像XML一樣簡單(很多人會說JSON會更加簡單),並且如果客戶端是JavaScript(最近一段時間以來,這種做法越來越常見)的話,JSON更是會成為優勝者,因為在JavaScript中使用JSON數據根本就不需要編排和解排(marshaling/demarshaling)

 

 

需要了解的是控制器本身通常並不關心資源如何表述。控制器以Java對象的方式來處理資源。控制器完成了它的工作之后,資源才會被轉化成最適合客戶端的形式。Spring提供了兩種方法將資源的Java表述形式轉換為發送給客戶端的表述形式:

內容協商(Content negotiation):選擇一個視圖,它能夠將模型
渲染為呈現給客戶端的表述形式;
消息轉換器(Message conversion):通過一個消息轉換器將控
制器所返回的對象轉換為呈現給客戶端的表述形式。

 

協商資源表述

 

你可以回憶一下在第5章中(以及圖5.1所示),當控制器的處理方法完成時,通常會返回一個邏輯視圖名。如果方法不直接返回邏輯視圖名(例如方法返回void),那么邏輯視圖名會根據請求的URL判斷得出。DispatcherServlet接下來會將視圖的名字傳遞給一個視圖解析器,要求它來幫助確定應該用哪個視圖來渲染請求結果。在面向人類訪問的Web應用程序中,選擇的視圖通常來講都會渲染為HTML。視圖解析方案是個簡單的一維活動。如果根據視圖名匹配上了視圖,那這就是我們要用的視圖了。當要將視圖名解析為能夠產生資源表述的視圖時,我們就有另外一個維度需要考慮了。視圖不僅要匹配視圖名,而且所選擇的視圖要適合客戶端。如果客戶端想要JSON,那么渲染HTML的視圖就不行了——盡管視圖名可能匹配。

Spring的ContentNegotiatingViewResolver是一個特殊的視圖解析器,它考慮到了客戶端所需要的內容類型。按照其最簡單的形式ContentNegotiatingViewResolver可以按照下述形式進行配置:

 

 在這個簡單的bean聲明背后會涉及到很多事情。要理解ContentNegotiating-ViewResolver是如何工作的,這涉及內容協商的兩個步驟

 

1.確定請求的媒體類型;
2.找到適合請求媒體類型的最佳視圖。

 

 

確定請求的媒體類型

  在內容協商兩步驟中,第一步是確定客戶端想要什么類型的內容表述。表面上看,這似乎是一個很簡單的事情。難道請求的Accept頭部信息不是已經很清楚地表明要發送什么樣的表述給客戶端嗎?遺憾的是,Accept頭部信息並不總是可靠的。如果客戶端是Web瀏覽器,那並不能保證客戶端需要的類型就是瀏覽器在Accept頭部所發送的值。Web瀏覽器一般只接受對人類用戶友好的內容類型(如text/html),所以沒有辦法(除了面向開發人員的瀏覽器插件)指定不同的內容類型。

  ContentNegotiatingViewResolver將會考慮到Accept頭部信息並使用它所請求的媒體類型,但是它會首先查看URL的文件擴展名。如果URL在結尾處有文件擴展名的話,ContentNegotiatingViewResolver將會基於該擴展名確定所需的類型。如果擴展名是“.json”的話,那么所需的內容類型必須是“application/json”。如果擴展名是“.xml”,那么客戶端請求的就是“application/xml”。當然,“.html”擴展名表明客戶端所需的資源表述為HTML(text/html)。如果根據文件擴展名不能得到任何媒體類型的話,那就會考慮請求中的Accept頭部信息。在這種情況下,Accept頭部信息中的值就表明了客戶端想要的MIME類型,沒有必要再去查找了。最后,如果沒有Accept頭部信息,並且擴展名也無法提供幫助的話,ContentNegotiatingViewResolver將會使用“/”作為默認的內容類型,這就意味着客戶端必須要接收服務器發送的任何形式的表述。一旦內容類型確定之后,ContentNegotiatingViewResolver就該將邏輯視圖名解析為渲染模型的View。

  與Spring的其他視圖解析器不同,ContentNegotiatingViewResolver本身不會解析視圖。而是委托給其他的視圖解析器,讓它們來解析視圖。ContentNegotiatingViewResolver要求其他的視圖解析器將邏輯視圖名解析為視圖。解析得到的每個視圖都會放到一個列表中。這個列表裝配完成后,ContentNegotiatingViewResolver會循環客戶端請求的所有媒體類型,在候選的視圖中查找能夠產生對應內容類型的視圖。第一個匹配的視圖會用來渲染模型。

 

 

影響媒體類型的選擇

在上述的選擇過程中,我們闡述了確定所請求媒體類型的默認策略。但是通過為其設置一個ContentNegotiationManager,我們能夠改變它的行為。借助Content-NegotiationManager我們所能做到的事情如下所示:

 

1.指定默認的內容類型,如果根據請求無法得到內容類型的話,將會使用默認值;

2.通過請求參數指定內容類型;

3.忽視請求的Accept頭部信息;
4.將請求的擴展名映射為特定的媒體類型;
5.
將JAF(Java Activation Framework)作為根據擴展名查找媒體類

型的備用方案

 

有三種配置ContentNegotiationManager的方法:

直接聲明一個ContentNegotiationManager類型的bean;

通過ContentNegotiationManagerFactoryBean間接創建bean;

重載WebMvcConfigurerAdapter的configureContentNegotiation()方法。

直接創建ContentNegotiationManager有一些復雜,除非有充分的原因,否則我們不會願意這樣做。后兩種方案能夠讓創建ContentNegotiationManager更加簡單。

 

一般而言,如果我們使用XML配置ContentNegotiationManager的話,那最有用的將會是ContentNegotiationManagerFactoryBean。例如,我們可能希望在XML中配置ContentNegotiationManager使

用“application/json”作為默認的內容類型:

因為ContentNegotiationManagerFactoryBean是FactoryBean的實現,所以它會創建一個ContentNegotiationManagerbean。這個ContentNegotiationManager能夠注入到ContentNegotiatingViewResolver的contentNegotiationManager屬性中。

 

 如果使用Java配置的話,獲得ContentNegotiationManager的最簡便方法就是擴展WebMvcConfigurerAdapter並重載configureContentNegotiation()方法。在創建Spring MVC應用的時候,我們很可能已經擴展了WebMvcConfigurerAdapter。例如,在Spittr應用中,我們已經有了WebMvcConfigurerAdapter的擴展類,名為WebConfig,所以需要做的就是重載configureContentNegotiation()方法。如下就是configureContentNegotiation()的一個實現,它設置了默認的內容類型:

 

我們可以看到,configureContentNegotiation()方法給定了一個Content-NegotiationConfigurer對象。ContentNegotiationConfigurer中的一些方法對應於ContentNegotiationManager的Setter方法,這樣我們就能在ContentNegotiation-Manager創建時,設置任意內容協商相關的屬性。在本例中,我們調用defaultContentType()方法將默認的內容類型設置為“application/json”。

配置ContentNegotiationManager有很多的細節,在這里無法對它們進行一一介紹。如下的程序清單是一個非常簡單的配置樣例,當我使用ContentNegotiating-ViewResolver的時候,通常會采用這種用法:它默認會使用HTML視圖,但是對特定的視圖名稱將會渲染為JSON輸出。

 

  除了程序清單16.2中的內容以外,還應該有一個能夠處理HTML的視圖解析器(如InternalResourceViewResolver或TilesViewResolver)。在大多數場景下,ContentNegotiatingViewResolver會假設客戶端需要HTML,如ContentNegotiationManager配置所示。但是,如果客戶端指定了它想要JSON(通過在請求路徑上使用“.json”擴展名或Accept頭部信息)的話,那么ContentNegotiatingViewResolver將會查找能夠處理JSON視圖的視圖解析器。如果邏輯視圖的名稱為“spittles”,那么我們所配置的BeanNameViewResolver將會解析spittles()方法中所聲明的View。這是因為bean名稱匹配邏輯視圖的名稱。如果沒有匹配的View的話ContentNegotiatingViewResolver將會采用默認的行為,將其輸出為HTML。ContentNegotiatingViewResolver一旦能夠確定客戶端想要什么樣的媒體類型,接下來就是查找渲染這種內容的視圖

 

5.使用HTTP信息轉換器

消息轉換(message conversion)提供了一種更為直接的方式,它能夠將控制器產生的數據轉換為服務於客戶端的表述形式。當使用消息轉換功能時,DispatcherServlet不再需要那么麻煩地將模型數據傳送到視圖中。實際上,這里根本就沒有模型,也沒有視圖,只有控制器產生的數據,以及消息轉換器(message converter)轉換數據之后所產生的資源表述。

 

  例如,假設客戶端通過請求的Accept頭信息表明它能接受“application/json”,並且Jackson JSON在類路徑下,那么處理方法返回的對象將交給MappingJacksonHttp-MessageConverter,並由它轉換為返回客戶端的JSON表述形式。另一方面,如果請求的頭信息表明客戶端想要“text/xml”格式,那么Jaxb2RootElementHttpMessage-Converter將會為客戶端產生XML響應。

  注意,表16.2中的HTTP信息轉換器除了其中的五個以外都是自動注冊的,所以要使用它們的話,不需要Spring配置。但是為了支持它們,你需要添加一些庫到應用程序的類路徑下。例如,如果你想使用MappingJacksonHttpMessageConverter來實現JSON消息和Java對象的互相轉換,那么需要將Jackson JSON Processor庫添加到類路徑中。類似地,如果你想使用Jaxb2RootElementHttpMessageConverter來實現XML消息和Java對象的互相轉換,那么需要JAXB庫。如果信息是Atom或RSS格式的話,那么Atom-FeedHttpMessageConverter和RssChannelHttpMessageConverter會需要Rome庫

 

你可能已經猜到了,為了支持消息轉換,我們需要對Spring MVC的編程模型進行一些小調整。在響應體中返回資源狀態正常情況下,當處理方法返回Java對象(除String外或View的實現以外)時,這個對象會放在模型中並在視圖中渲染使用。但是,如果使用了消息轉換功能的話,我們需要告訴Spring跳過正常的模型/視圖流程,並使用消息轉換器。有不少方式都能做到這一點,但是最簡單的方法是為控制器方法添加@ResponseBody注解。

 

 

 

在請求體中接收資源狀態
到目前為止,我們只關注了REST端點如何為客戶端提供資源。但是
REST並不是只讀的,REST API也可以接受來自客戶端的資源表述。
如果要讓控制器將客戶端發送的JSON和XML轉換為它所使用的Java
對象,那是非常不方便的。在處理邏輯離開控制器的時候,Spring的
消息轉換器能夠將對象轉換為表述——它們能不能在表述傳入的時候
完成相同的任務呢?
@ResponseBody能夠告訴Spring在把數據發送給客戶端的時候,要
使用某一個消息器,與之類似,@RequestBody也能告訴Spring查找
一個消息轉換器,將來自客戶端的資源表述轉換為對象。例如,假設
我們需要一種方式將客戶端提交的新Spittle保存起來。我們可以
按照如下的方式編寫控制器方法來處理這種請求

 

 

 

@ResponseBody注解會告知Spring,我們要將返回的對象作為資源發送給客戶端,並將其轉換為客戶端可接受的表述形式。更具體地講,DispatcherServlet將會考慮到請求中Accept頭部信息,並查找能夠為客戶端提供所需表述形式的消息轉換器。舉例來講,假設客戶端的Accept頭部信息表明它接受“application/json”,並且Jackson JSON庫位於應用的類路徑下,那么將會選擇MappingJacksonHttpMessage-Converter或MappingJackson2HttpMessageConverter(這取決於類路徑下是哪個版本的Jackson)。消息轉換器會將控制器返回的Spittle列表轉換為JSON文檔,並將其寫入到響應體中。響應大致會如下所示

 

在請求體中接收資源狀態到目前為止,我們只關注了REST端點如何為客戶端提供資源。但是REST並不是只讀的,REST API也可以接受來自客戶端的資源表述。如果要讓控制器將客戶端發送的JSON和XML轉換為它所使用的Java對象,那是非常不方便的。在處理邏輯離開控制器的時候,Spring的消息轉換器能夠將對象轉換為表述——它們能不能在表述傳入的時候完成相同的任務呢?

@ResponseBody能夠告訴Spring在把數據發送給客戶端的時候,要使用某一個消息器,與之類似,@RequestBody也能告訴Spring查找一個消息轉換器,將來自客戶端的資源表述轉換為對象。例如,假設我們需要一種方式將客戶端提交的新Spittle保存起來。我們可以按照如下的方式編寫控制器方法來處理這種請求

@RequestBody,所以Spring將會查看請求中的Content-Type頭部信息,並查找能夠將請求體轉換為Spittle的消息轉換器。例如,如果客戶端發送的Spittle數據是JSON表述形式,那么Content-Type頭部信息可能就會是“application/json”。在這種情況下,DispatcherServlet會查找能夠將JSON轉換為Java對象的消息轉換器。如果Jackson 2庫在類路徑中,那么MappingJackson2HttpMessageConverter將會擔此重任,將JSON表述轉換為Spittle,然后傳遞到saveSpittle()方法中。這個方法還使用了@ResponseBody注解,因此方法返回的Spittle對象將會轉換為某種資源表述,發送給客戶端。注意,@RequestMapping有一個consumes屬性,我們將其設置為“application/ json”。consumes屬性的工作方式類似於produces,不過它會關注請求的Content-Type頭部信息。它會告訴Spring這個方法只會處理對“/spittles”的POST請求,並且要求請求的Content-Type頭部信息為“application/json”。如果無法滿足這些條件的話,會由其他方法(如果存在合適的方法的話)來處理請求。

 

為控制器默認設置消息轉換

 

當處理請求時,@ResponseBody和@RequestBody是啟用消息轉換的一種簡潔和強大方式。但是,如果你所編寫的控制器有多個方法,並且每個方法都需要信息轉換功能的話,那么這些注解就會帶來一定程度的重復性。Spring 4.0引入了@RestController注解,能夠在這個方面給我們提供幫助。如果在控制器類上使用@RestController來代替@Controller的話,Spring將會為該控制器的所有處理方法應用消息轉換功能。我們不必為每個方法都添加@ResponseBody了。我們所定義的SpittleController可能就會如下所示:

 我們看到了如何使用Spring MVC編程模型將RESTful資源發布到響應體之中。但是響應除了負載以外還會有其他的內容。頭部信息和狀態碼也能夠為客戶端提供響應的有用信息。接下來,我們

看一下在提供資源的時候,如何填充頭部信息和設置狀態碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM