使用Spring MVC建立 REST API

 

1.REST的基礎知識

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

 

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

狀態(State):當使用REST的時候,咱們更關注資源的狀態而不是對資源採起的行爲;json

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

REST中會有行爲,它們是經過HTTP方法來定義的。具體來說,也就是GET、POST、PUT、DELETE、PATCH以及其餘的HTTP方法構成了服務器

REST中的動做。這些HTTP方法一般會匹配爲以下的CRUD動做:app

Create:POSTRead:GETide

Update:PUT或PATCH學習

Delete:DELETEspa

 

 

儘管一般來說,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資源發佈到響應體之中。可是響應除了負載之外還會有其餘的內容。頭部信息和狀態碼也可以爲客戶端提供響應的有用信息。接下來,咱們

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

相關文章
相關標籤/搜索