javascript
使用 Spring HATEOAS 开发 REST 服务--转
原文地址:https://www.ibm.com/developerworks/cn/java/j-lo-SpringHATEOAS/index.html?ca=drs-&utm_source=tuicool&utm_medium=referral
絕大多數(shù)開發(fā)人員對(duì)于 REST 這個(gè)詞都并不陌生。自從 2000 年 Roy Fielding 在其博士論文中創(chuàng)造出來這個(gè)詞之后,REST 架構(gòu)風(fēng)格就很快地流行起來,已經(jīng)成為了構(gòu)建 Web 服務(wù)時(shí)應(yīng)該遵循的事實(shí)標(biāo)準(zhǔn)。很多 Web 服務(wù)和 API 都宣稱滿足了 REST 架構(gòu)風(fēng)格的要求,即所謂的“RESTful”服務(wù)。不過就如同其他很多流行的概念一樣,不少人對(duì)于 REST 的含義還是存在或多或少的種種誤解。REST 在某些時(shí)候被當(dāng)成了一種營(yíng)銷的手段。不少所謂的“RESTful” Web 服務(wù)或 API 實(shí)際上并不滿足 REST 架構(gòu)風(fēng)格的要求。這其中的部分原因在于 REST 的含義比較復(fù)雜,包含很多不同方面的內(nèi)容。本文首先對(duì) REST 架構(gòu)做一個(gè)簡(jiǎn)單的說明以澄清某些誤解。
REST 架構(gòu)
REST 是 Representational state transfer 的縮寫,翻譯過來的意思是表達(dá)性狀態(tài)轉(zhuǎn)換。REST 是一種架構(gòu)風(fēng)格,它包含了一個(gè)分布式超文本系統(tǒng)中對(duì)于組件、連接器和數(shù)據(jù)的約束。REST 是作為互聯(lián)網(wǎng)自身架構(gòu)的抽象而出現(xiàn)的,其關(guān)鍵在于所定義的架構(gòu)上的各種約束。只有滿足這些約束,才能稱之為符合 REST 架構(gòu)風(fēng)格。REST 的約束包括:
- 客戶端-服務(wù)器結(jié)構(gòu)。通過一個(gè)統(tǒng)一的接口來分開客戶端和服務(wù)器,使得兩者可以獨(dú)立開發(fā)和演化。客戶端的實(shí)現(xiàn)可以簡(jiǎn)化,而服務(wù)器可以更容易的滿足可伸縮性的要求。
- 無狀態(tài)。在不同的客戶端請(qǐng)求之間,服務(wù)器并不保存客戶端相關(guān)的上下文狀態(tài)信息。任何客戶端發(fā)出的每個(gè)請(qǐng)求都包含了服務(wù)器處理該請(qǐng)求所需的全部信息。
- 可緩存。客戶端可以緩存服務(wù)器返回的響應(yīng)結(jié)果。服務(wù)器可以定義響應(yīng)結(jié)果的緩存設(shè)置。
- 分層的系統(tǒng)。在分層的系統(tǒng)中,可能有中間服務(wù)器來處理安全策略和緩存等相關(guān)問題,以提高系統(tǒng)的可伸縮性。客戶端并不需要了解中間的這些層次的細(xì)節(jié)。
- 按需代碼(可選)。服務(wù)器可以通過傳輸可執(zhí)行代碼的方式來擴(kuò)展或自定義客戶端的行為。這是一個(gè)可選的約束。
- 統(tǒng)一接口。該約束是 REST 服務(wù)的基礎(chǔ),是客戶端和服務(wù)器之間的橋梁。該約束又包含下面 4 個(gè)子約束。
- 資源標(biāo)識(shí)符。每個(gè)資源都有各自的標(biāo)識(shí)符。客戶端在請(qǐng)求時(shí)需要指定該標(biāo)識(shí)符。在 REST 服務(wù)中,該標(biāo)識(shí)符通常是 URI。客戶端所獲取的是資源的表達(dá)(representation),通常使用 XML 或 JSON 格式。
- 通過資源的表達(dá)來操縱資源。客戶端根據(jù)所得到的資源的表達(dá)中包含的信息來了解如何操縱資源,比如對(duì)資源進(jìn)行修改或刪除。
- 自描述的消息。每條消息都包含足夠的信息來描述如何處理該消息。
- 超媒體作為應(yīng)用狀態(tài)的引擎(HATEOAS)。客戶端通過服務(wù)器提供的超媒體內(nèi)容中動(dòng)態(tài)提供的動(dòng)作來進(jìn)行狀態(tài)轉(zhuǎn)換。這也是本文所要介紹的內(nèi)容。
在了解 REST 的這些約束之后,就可以對(duì)“表達(dá)性狀態(tài)轉(zhuǎn)換”的含義有更加清晰的了解。“表達(dá)性”的含義是指對(duì)于資源的操縱都是通過服務(wù)器提供的資源的表達(dá)來進(jìn)行的。客戶端在根據(jù)資源的標(biāo)識(shí)符獲取到資源的表達(dá)之后,從資源的表達(dá)中可以發(fā)現(xiàn)其可以使用的動(dòng)作。使用這些動(dòng)作會(huì)發(fā)出新的請(qǐng)求,從而觸發(fā)狀態(tài)轉(zhuǎn)換。
HATEOAS 約束
HATEOAS(Hypermedia as the engine of application state)是 REST 架構(gòu)風(fēng)格中最復(fù)雜的約束,也是構(gòu)建成熟 REST 服務(wù)的核心。它的重要性在于打破了客戶端和服務(wù)器之間嚴(yán)格的契約,使得客戶端可以更加智能和自適應(yīng),而 REST 服務(wù)本身的演化和更新也變得更加容易。
在介紹 HATEOAS 之前,先介紹一下 Richardson 提出的 REST 成熟度模型。該模型把 REST 服務(wù)按照成熟度劃分成 4 個(gè)層次:
- 第一個(gè)層次(Level 0)的 Web 服務(wù)只是使用 HTTP 作為傳輸方式,實(shí)際上只是遠(yuǎn)程方法調(diào)用(RPC)的一種具體形式。SOAP 和 XML-RPC 都屬于此類。
- 第二個(gè)層次(Level 1)的 Web 服務(wù)引入了資源的概念。每個(gè)資源有對(duì)應(yīng)的標(biāo)識(shí)符和表達(dá)。
- 第三個(gè)層次(Level 2)的 Web 服務(wù)使用不同的 HTTP 方法來進(jìn)行不同的操作,并且使用 HTTP 狀態(tài)碼來表示不同的結(jié)果。如 HTTP GET 方法來獲取資源,HTTP DELETE 方法來刪除資源。
- 第四個(gè)層次(Level 3)的 Web 服務(wù)使用 HATEOAS。在資源的表達(dá)中包含了鏈接信息。客戶端可以根據(jù)鏈接來發(fā)現(xiàn)可以執(zhí)行的動(dòng)作。
從上述 REST 成熟度模型中可以看到,使用 HATEOAS 的 REST 服務(wù)是成熟度最高的,也是推薦的做法。對(duì)于不使用 HATEOAS 的 REST 服務(wù),客戶端和服務(wù)器的實(shí)現(xiàn)之間是緊密耦合的。客戶端需要根據(jù)服務(wù)器提供的相關(guān)文檔來了解所暴露的資源和對(duì)應(yīng)的操作。當(dāng)服務(wù)器發(fā)生了變化時(shí),如修改了資源的 URI,客戶端也需要進(jìn)行相應(yīng)的修改。而使用 HATEOAS 的 REST 服務(wù)中,客戶端可以通過服務(wù)器提供的資源的表達(dá)來智能地發(fā)現(xiàn)可以執(zhí)行的操作。當(dāng)服務(wù)器發(fā)生了變化時(shí),客戶端并不需要做出修改,因?yàn)橘Y源的 URI 和其他信息都是動(dòng)態(tài)發(fā)現(xiàn)的。
回頁(yè)首
示例
本文將通過一個(gè)完整的示例來說明 HATEOAS。該示例是一個(gè)常見的待辦事項(xiàng)的服務(wù),用戶可以創(chuàng)建新的待辦事項(xiàng)、進(jìn)行編輯或標(biāo)記為已完成。該示例中包含的資源如下:
- 用戶:應(yīng)用中的用戶。
- 列表:待辦事項(xiàng)的列表,屬于某個(gè)用戶。
- 事項(xiàng):具體的待辦事項(xiàng),屬于某個(gè)列表。
應(yīng)用提供相關(guān)的 REST 服務(wù)來完成對(duì)于列表和事項(xiàng)兩個(gè)資源的 CRUD 操作。
回頁(yè)首
Spring HATEOAS
如果 Web 應(yīng)用基于 Spring 框架開發(fā),那么可以直接使用 Spring 框架的子項(xiàng)目 HATEOAS 來開發(fā)滿足 HATEOAS 約束的 Web 服務(wù)。本文的示例應(yīng)用基于 Java 8 和使用 Spring Boot 1.1.9 來創(chuàng)建,Spring HATEOAS 的版本是 0.16.0.RELEASE。
基本配置
滿足 HATEOAS 約束的 REST 服務(wù)最大的特點(diǎn)在于服務(wù)器提供給客戶端的表達(dá)中包含了動(dòng)態(tài)的鏈接信息,客戶端通過這些鏈接來發(fā)現(xiàn)可以觸發(fā)狀態(tài)轉(zhuǎn)換的動(dòng)作。Spring HATEOAS 的主要功能在于提供了簡(jiǎn)單的機(jī)制來創(chuàng)建這些鏈接,并與 Spring MVC 框架有很好的集成。對(duì)于已有的 Spring MVC 應(yīng)用,只需要一些簡(jiǎn)單的改動(dòng)就可以滿足 HATEOAS 約束。對(duì)于一個(gè) Maven 項(xiàng)目來說,只需要添加代碼清單 1中的依賴即可。
清單 1. Spring HATEOAS 的 Maven 依賴聲明
<dependency><groupId>org.springframework.hateoas</groupId><artifactId>spring-hateoas</artifactId><version>0.16.0.RELEASE</version> </dependency>資源
REST 架構(gòu)中的核心概念之一是資源。服務(wù)器提供的是資源的表達(dá),通常使用 JSON 或 XML 格式。在一般的 Web 應(yīng)用中,服務(wù)器端代碼會(huì)對(duì)所使用的資源建模,提供相應(yīng)的模型層 Java 類,這些模型層 Java 類通常包含 JPA 相關(guān)的注解來完成持久化。在客戶端請(qǐng)求時(shí),服務(wù)器端代碼通過 Jackson 或 JAXB 把模型對(duì)象轉(zhuǎn)換成 JSON 或 XML 格式。代碼清單 2給出了示例應(yīng)用中表示列表的模型類 List 的聲明。
清單 2. 表示列表的模型類 List 的聲明
@Entity public class List extends AbstractEntity {private String name;@ManyToOne@JsonIgnoreprivate User user;@OneToMany(mappedBy = "list", fetch = FetchType.LAZY)@JsonIgnoreprivate Set<Item> items = new HashSet<>();protected List() {}public List(String name, User user) {this.name = name;this.user = user;}public String getName() {return name;}public User getUser() {return user;}public Set<Item> getItems() {return items;} }當(dāng)客戶端請(qǐng)求某個(gè)具體的 List 類的對(duì)象時(shí),服務(wù)器端返回如代碼清單 3所示的 JSON 格式的表達(dá)。
清單 3. List 類的對(duì)象的 JSON 格式的表達(dá)
{"id": 1,"name": "Default" }在代碼清單 3中,服務(wù)器端返回的只是模型類對(duì)象本身的內(nèi)容,并沒有提供相關(guān)的鏈接信息。為了把模型對(duì)象類轉(zhuǎn)換成滿足 HATEOAS 要求的資源,需要添加鏈接信息。Spring HATEOAS 使用 org.springframework.hateoas.Link 類來表示鏈接。Link 類遵循 Atom 規(guī)范中對(duì)于鏈接的定義,包含 rel 和 href 兩個(gè)屬性。屬性 rel 表示的是鏈接所表示的關(guān)系(relationship),href 表示的是鏈接指向的資源標(biāo)識(shí)符,一般是 URI。資源通常都包含一個(gè)屬性 rel 值為 self 的鏈接,用來指向該資源本身。
在創(chuàng)建資源類時(shí),可以繼承自 Spring HATEOAS 提供的 org.springframework.hateoas.Resource 類,Resource 類提供了簡(jiǎn)單的方式來創(chuàng)建鏈接。代碼清單 4給出了與模型類 List 對(duì)應(yīng)的資源類 ListResource 的聲明。
清單 4. 模型類 List 對(duì)應(yīng)的資源類 ListResource 的聲明
public class ListResource extends Resource {private final List list;public ListResource(List list) {super(list);this.list = list;add(new Link("http://localhost:8080/lists/1"));add(new Link("http://localhost:8080/lists/1/items", "items"));}public List getList() {return list;} }如代碼清單 4所示,ListResource 類繼承自 Resource 類并對(duì) List 類的對(duì)象進(jìn)行了封裝,添加了兩個(gè)鏈接。在使用 ListResource 類之后,服務(wù)器端返回的表達(dá)格式如代碼清單 5所示。
清單 5. 使用 ListResource 類之后的 JSON 格式的表達(dá)
{"list": {"id": 1,"name": "Default"},"links": [{"rel": "self","href": "http://localhost:8080/lists/1"},{"rel": "items","href": "http://localhost:8080/lists/1/items"}] }代碼清單 5的 JSON 內(nèi)容中添加了額外的 links 屬性,并包含了兩個(gè)鏈接。不過模型類對(duì)象的內(nèi)容被封裝在屬性 list 中。這是因?yàn)?ListResource 類直接封裝了整個(gè)的 List 類的對(duì)象,而不是把 List 類的屬性提取到 ListResource 類中。如果需要改變輸出的 JSON 表達(dá)的格式,可以使用另外一種封裝方式的 ListResource 類,如代碼清單 6所示。
清單 6. 不同封裝格式的 ListResource 類
public class ListResource extends Resource {private final Long id;private final String name;public ListResource(List list) {super(list);this.id = list.getId();this.name = list.getName();add(new Link("http://localhost:8080/lists/1"));add(new Link("http://localhost:8080/lists/1/items", "items"));}public Long getId() {return id;}public String getName() {return name;} }對(duì)應(yīng)的資源的表達(dá)如代碼清單 7所示。
清單 7. 使用不同封裝方式的 JSON 格式的表達(dá)
{"id": 1,"name": "Default","links": [{"rel": "self","href": "http://localhost:8080/lists/1"},{"rel": "items","href": "http://localhost:8080/lists/1/items"}] }這兩種不同的封裝方式各有優(yōu)缺點(diǎn)。第一種方式的優(yōu)點(diǎn)是實(shí)現(xiàn)起來很簡(jiǎn)單,只需要把模型層的對(duì)象直接包裝即可;第二種方式雖然實(shí)現(xiàn)起來相對(duì)比較復(fù)雜,但是可以對(duì)資源的表達(dá)格式進(jìn)行定制,使得資源的表達(dá)格式更直接。
在代碼實(shí)現(xiàn)中經(jīng)常會(huì)需要把模型類對(duì)象轉(zhuǎn)換成對(duì)應(yīng)的資源對(duì)象,如把 List 類的對(duì)象轉(zhuǎn)換成 ListResource 類的對(duì)象。一般的做法是通過“new ListResource(list)”這樣的方式來進(jìn)行轉(zhuǎn)換。可以使用 Spring HATEOAS 提供的資源組裝器把轉(zhuǎn)換的邏輯封裝起來。資源組裝器還可以自動(dòng)創(chuàng)建 rel 屬性為 self 的鏈接。代碼清單 8中給出了組裝資源類 ListResource 的 ListResourceAssembler 類的實(shí)現(xiàn)。
清單 8. 組裝資源類 ListResource 的 ListResourceAssembler 類的實(shí)現(xiàn)
public class ListResourceAssembler extends ResourceAssemblerSupport<List, ListResource> {public ListResourceAssembler() {super(ListRestController.class, ListResource.class);}@Overridepublic ListResource toResource(List list) {ListResource resource = createResourceWithId(list.getId(), list);return resource;}@Overrideprotected ListResource instantiateResource(List entity) {return new ListResource(entity);} }在創(chuàng)建 ListResourceAssembler 類的對(duì)象時(shí)需要指定使用資源的 Spring MVC 控制器 Java 類和資源 Java 類。對(duì)于 ListResourceAssembler 類來說分別是 ListRestController 和 ListResource。ListRestController 類在下一節(jié)中會(huì)具體介紹,其作用是用來創(chuàng)建 rel 屬性為 self 的鏈接。ListResourceAssembler 類的 instantiateResource 方法用來根據(jù)一個(gè)模型類 List 的對(duì)象創(chuàng)建出 ListResource 對(duì)象。ResourceAssemblerSupport 類的默認(rèn)實(shí)現(xiàn)是通過反射來創(chuàng)建資源對(duì)象的。toResource 方法用來完成實(shí)際的轉(zhuǎn)換。此處使用了 ResourceAssemblerSupport 類的 createResourceWithId 方法來創(chuàng)建一個(gè)包含 self 鏈接的資源對(duì)象。
在代碼中需要?jiǎng)?chuàng)建 ListResource 的地方,都可以換成使用 ListResourceAssembler,如代碼清單 9所示。
清單 9. 使用 ListResourceAssembler 的示例
//組裝單個(gè)資源對(duì)象 new ListResourceAssembler().toResource(list);//組裝資源對(duì)象的集合 new ListResourceAssembler().toResources(lists);代碼清單 9中的 toResources 方法是 ResourceAssemblerSupport 類提供的。當(dāng)需要轉(zhuǎn)換一個(gè)集合的資源對(duì)象時(shí),這個(gè)方法非常實(shí)用。
鏈接
HATEOAS 的核心是鏈接。鏈接的存在使得客戶端可以動(dòng)態(tài)發(fā)現(xiàn)其所能執(zhí)行的動(dòng)作。在上一節(jié)中介紹過鏈接由 rel 和 href 兩個(gè)屬性組成。其中屬性 rel 表明了該鏈接所代表的關(guān)系含義。應(yīng)用可以根據(jù)需要為鏈接選擇最適合的 rel 屬性值。由于每個(gè)應(yīng)用的情況并不相同,對(duì)于應(yīng)用相關(guān)的 rel 屬性值并沒有統(tǒng)一的規(guī)范。不過對(duì)于很多常見的鏈接關(guān)系,IANA 定義了規(guī)范的 rel 屬性值。在開發(fā)中可能使用的常見 rel 屬性值如表1所示。
表 1. 常用的 rel 屬性
| self | 指向當(dāng)前資源本身的鏈接的 rel 屬性。每個(gè)資源的表達(dá)中都應(yīng)該包含此關(guān)系的鏈接。 |
| edit | 指向一個(gè)可以編輯當(dāng)前資源的鏈接。 |
| item | 如果當(dāng)前資源表示的是一個(gè)集合,則用來指向該集合中的單個(gè)資源。 |
| collection | 如果當(dāng)前資源包含在某個(gè)集合中,則用來指向包含該資源的集合。 |
| related | 指向一個(gè)與當(dāng)前資源相關(guān)的資源。 |
| search | 指向一個(gè)可以搜索當(dāng)前資源及其相關(guān)資源的鏈接。 |
| first、last、previous、next | 這幾個(gè) rel 屬性值都有集合中的遍歷相關(guān),分別用來指向集合中的第一個(gè)、最后一個(gè)、上一個(gè)和下一個(gè)資源。 |
如果在應(yīng)用中使用自定義 rel 屬性值,一般的做法是屬性值全部為小寫,中間使用“-”分隔。
鏈接中另外一個(gè)重要屬性 href 表示的是資源的標(biāo)識(shí)符。對(duì)于 Web 應(yīng)用來說,通常是一個(gè) URL。URL 必須指向的是一個(gè)絕對(duì)的地址。在應(yīng)用中創(chuàng)建鏈接時(shí),在 URL 中使用硬編碼的主機(jī)名和端口號(hào)顯然不是好的選擇。Spring MVC 提供了相關(guān)的工具類可以獲取 Web 應(yīng)用啟動(dòng)時(shí)的主機(jī)名和端口號(hào),不過創(chuàng)建動(dòng)態(tài)的鏈接 URL 還需要可以獲取資源的訪問路徑。對(duì)于一個(gè)典型的 Spring MVC 控制器來說,其聲明如代碼清單 10所示。
清單 10. Spring MVC 控制器 ListRestController 類的實(shí)現(xiàn)
@RestController @RequestMapping("/lists") public class ListRestController {@Autowiredprivate ListService listService;@RequestMapping(method = RequestMethod.GET)public Resources<ListResource> readLists(Principal principal) {String username = principal.getName();return new Resources<ListResource>(new ListResourceAssembler().toResources(listService.findByUserUsername(username)));@RequestMapping(value = "/{listId}", method = RequestMethod.GET)public ListResource readList(@PathVariable Long listId) {return new ListResourceAssembler().toResource(listService.findOne(listId));} }從代碼清單 10中可以看到,Spring MVC 控制器 ListRestController 類通過“@RequestMapping”注解聲明了其訪問路徑是“/lists”,而訪問單個(gè)資源的路徑是類似“/lists/1”這樣的形式。在創(chuàng)建資源的鏈接時(shí),指向單個(gè)資源的鏈接的 href 屬性值是類似“http://localhost:8080/lists/1”這樣的格式。而其中的“/lists”不應(yīng)該是硬編碼的,否則當(dāng)修改了 ListRestController 類的“@RequestMapping”時(shí),所有相關(guān)的生成鏈接的代碼都需要進(jìn)行修改。Spring HATEOAS 提供了 org.springframework.hateoas.mvc.ControllerLinkBuilder 來解決這個(gè)問題,用來根據(jù) Spring MVC 控制器動(dòng)態(tài)生成鏈接。代碼清單 11給出了創(chuàng)建單個(gè)資源的鏈接的方式。
清單 11. 使用 ControllerLinkBuilder 類創(chuàng)建鏈接
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;Link link = linkTo(ListRestController.class).slash(listId).withSelfRel();通過 ControllerLinkBuilder 類的 linkTo 方法,先指定 Spring MVC 控制器的 Java 類,再通過 slash 方法來找到下一級(jí)的路徑,最后生成屬性值為 self 的鏈接。在使用 ControllerLinkBuilder 生成鏈接時(shí),除了可以使用控制器的 Java 類之外,還可以使用控制器 Java 類中包含的方法。如代碼清單 12所示。
清單 12. 通過控制器 Java 類中的方法生成鏈接
Link link = linkTo(methodOn(ItemRestController.class).readItems(listId)).withRel("items");代碼清單 12中的鏈接使用的是 ItemRestController 類中的 readItems 方法。參數(shù) listId 是組成 URI 的一部分,在調(diào)用 readItems 方法時(shí)需要提供。
上面介紹的是通過 Spring MVC 控制器來創(chuàng)建鏈接,另外一種做法是從模型類中創(chuàng)建。這是因?yàn)榭刂破魍ǔS脕肀┞赌硞€(gè)模型類。如 ListRestController 類直接暴露模型類 List,并提供了訪問 List 資源集合和單個(gè) List 資源的接口。對(duì)于這樣的情況,并不需要通過控制器來創(chuàng)建相關(guān)的鏈接,而可以使用 EntityLinks。
首先需要在控制器類中通過“@ExposesResourceFor”注解聲明其所暴露的模型類,如代碼清單 13中的 ListRestController 類的聲明。
清單 13. “@ExposesResourceFor”注解的使用
@RestController @ExposesResourceFor(List.class) @RequestMapping("/lists") public class ListRestController {}另外在 Spring 應(yīng)用的配置類中需要通過“@EnableEntityLinks”注解來啟用 EntityLinks 功能。此外還需要添加代碼清單 14中給出的 Maven 依賴。
清單 14. EntityLinks 功能所需的 Maven 依賴
<dependency><groupId>org.springframework.plugin</groupId><artifactId>spring-plugin-core</artifactId><version>1.1.0.RELEASE</version> </dependency>在需要?jiǎng)?chuàng)建鏈接的代碼中,只需要通過依賴注入的方式添加對(duì) EntityLinks 的引用,就可以使用 linkForSingleResource 方法來創(chuàng)建指向單個(gè)資源的鏈接,如代碼清單 15所示。
清單 15. 使用 EntityLinks 創(chuàng)建鏈接
@Autowired private EntityLinks entityLinks;entityLinks.linkForSingleResource(List.class, 1) ?需要注意的是,為了 linkForSingleResource 方法可以正常工作,控制器類中需要包含訪問單個(gè)資源的方法,而且其“@RequestMapping”是類似“/{id}”這樣的形式。
回頁(yè)首
超媒體控制與 HAL
在添加了鏈接之后,服務(wù)器端提供的表達(dá)可以幫助客戶端更好的發(fā)現(xiàn)服務(wù)器端所支持的動(dòng)作。在具體的表達(dá)中,應(yīng)用雖然可以根據(jù)需要選擇最適合的格式,但是在表達(dá)的基本結(jié)構(gòu)上應(yīng)該遵循一定的規(guī)范,這樣可以保證最大程度的適用性。這個(gè)基本結(jié)構(gòu)主要是整體的組織方式和鏈接的格式。HAL(Hypertxt Application Language)是一個(gè)被廣泛采用的超文本表達(dá)的規(guī)范。應(yīng)用可以考慮遵循該規(guī)范,Spring HATEOAS 提供了對(duì) HAL 的支持。
HAL 規(guī)范
HAL 規(guī)范本身是很簡(jiǎn)單的,代碼清單 16給出了示例的 JSON 格式的表達(dá)。
清單 16. HAL 規(guī)范的示例 JSON 格式的表達(dá)
{"_links": {"self": {"href": "http://localhost:8080/lists"}},"_embedded": {"lists": [{"id": 1,"name": "Default","_links": {"todo:items": {"href": "http://localhost:8080/lists/1/items"},"self": {"href": "http://localhost:8080/lists/1"},"curies": [{"href": "http://www.midgetontoes.com/todolist/rels/{rel}","name": "todo","templated": true}]}}]} }HAL 規(guī)范圍繞資源和鏈接這兩個(gè)簡(jiǎn)單的概念展開。資源的表達(dá)中包含鏈接、嵌套的資源和狀態(tài)。資源的狀態(tài)是該資源本身所包含的數(shù)據(jù)。鏈接則包含其指向的目標(biāo)(URI)、所表示的關(guān)系和其他可選的相關(guān)屬性。對(duì)應(yīng)到 JSON 格式中,資源的鏈接包含在_links 屬性對(duì)應(yīng)的哈希對(duì)象中。該_links 哈希對(duì)象中的鍵(key)是鏈接的關(guān)系,而值(value)則是另外一個(gè)包含了 href 等其他鏈接屬性的對(duì)象或?qū)ο髷?shù)組。當(dāng)前資源中所包含的嵌套資源由_embeded 屬性來表示,其值是一個(gè)包含了其他資源的哈希對(duì)象。
鏈接的關(guān)系不僅是區(qū)分不同鏈接的標(biāo)識(shí)符,同樣也是指向相關(guān)文檔的 URL。文檔用來告訴客戶端如何對(duì)該鏈接所指向的資源進(jìn)行操作。當(dāng)開發(fā)人員獲取到了資源的表達(dá)之后,可以通過查看鏈接指向的文檔來了解如何操作該資源。
使用 URL 作為鏈接的關(guān)系帶來的問題是 URL 作為屬性名稱來說顯得過長(zhǎng),而且不同關(guān)系的 URL 的大部分內(nèi)容是重復(fù)的。為了解決這個(gè)問題,可以使用 Curie。簡(jiǎn)單來說,Curie 可以作為鏈接關(guān)系 URL 的模板。鏈接的關(guān)系聲明時(shí)使用 Curie 的名稱作為前綴,不用提供完整的 URL。應(yīng)用中聲明的 Curie 出現(xiàn)在_links 屬性中。代碼中定義了 URI 模板為“http://www.midgetontoes.com/todolist/rels/{rel}”的名為 todo 的 Curie。在使用了 Curie 之后,名為 items 的鏈接關(guān)系變成了包含前綴的“todo:items”的形式。這就表示該鏈接的關(guān)系實(shí)際上是“http://www.midgetontoes.com/todolist/rels/items”。
Spring HATEOAS 的 HAL 支持
目前 Spring HATEOAS 僅支持 HAL 一種超媒體表達(dá)格式,只需要在應(yīng)用的配置類上添加“@EnableHypermediaSupport(type= {HypermediaType.HAL})”注解就可以啟用該超媒體支持。在啟用了超媒體支持之后,服務(wù)器端輸出的表達(dá)格式會(huì)遵循 HAL 規(guī)范。另外,啟用超媒體支持會(huì)默認(rèn)啟用“@EnableEntityLinks”。在啟用超媒體支持之后,應(yīng)用需要進(jìn)行相關(guān)的定制使得生成的 HAL 表達(dá)更加友好。
首先是內(nèi)嵌資源在_embedded 對(duì)應(yīng)的哈希對(duì)象中的屬性值,該屬性值是由 org.springframework.hateoas.RelProvider 接口的實(shí)現(xiàn)來提供的。對(duì)于應(yīng)用來說,只需要在內(nèi)嵌資源對(duì)應(yīng)的模型類中添加 org.springframework.hateoas.core.Relation 注解即可,如代碼清單 17所示。
清單 17. 在模型類中添加 @Relation 注解
@Relation(value = "list", collectionRelation = "lists") public class List extends AbstractEntity { }代碼清單 17中聲明了當(dāng)模型類 List 的對(duì)象作為內(nèi)嵌資源時(shí),單個(gè)資源使用 list 作為屬性值,多個(gè)資源使用 lists 作為屬性值。
如果需要添加 Curie,則提供 org.springframework.hateoas.hal.CurieProvider 接口的實(shí)現(xiàn),如代碼清單 18所示。利用已有的 org.springframework.hateoas.hal.DefaultCurieProvider 類并提供 Curie 的前綴和 URI 模板即可。
清單 18. 添加 CurieProvider 接口的實(shí)現(xiàn)
@Bean public CurieProvider curieProvider() {return new DefaultCurieProvider("todo",new UriTemplate("http://www.midgetontoes.com/todolist/rels/{rel}")); }回頁(yè)首
結(jié)束語(yǔ)
在開發(fā)一個(gè)新的 Web 服務(wù)或 API 時(shí),REST 架構(gòu)風(fēng)格已經(jīng)成為事實(shí)上的標(biāo)準(zhǔn)。在開發(fā)時(shí)需要明白 REST 架構(gòu)風(fēng)格中所包含的約束的含義。HATEOAS 作為 REST 服務(wù)約束中最復(fù)雜的一個(gè),目前還沒有得到廣泛的使用。但是采用 HATEOAS 所帶來的好處是很大的,可以幫助客戶端和服務(wù)器更好的解耦,可以減少很多潛在的問題。Spring HATEOAS 在 Spring MVC 框架的基礎(chǔ)上,允許開發(fā)人員通過簡(jiǎn)單的配置來添加 HATEOAS 約束。如果應(yīng)用本身已經(jīng)使用了 Spring MVC,則同時(shí)啟用 HATEOAS 是一個(gè)很好的選擇。本文對(duì) REST 和 HATEOAS 的相關(guān)概念以及 Spring HATEOAS 框架的使用做了詳細(xì)的介紹。
回頁(yè)首
下載
| sample_code.zip | 26k |
參考資料
學(xué)習(xí)
- 參考REST和HATEOAS的維基百科。
- 了解 Richardson 提出的REST 成熟度模型和 Martin Fowler 對(duì)此的相關(guān)介紹。
- 查看 Spring HATEOAS 的官方網(wǎng)站。
- 了解 IANA 定義的鏈接關(guān)系。
- 了解HAL 規(guī)范的具體內(nèi)容。
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/6673105.html
總結(jié)
以上是生活随笔為你收集整理的使用 Spring HATEOAS 开发 REST 服务--转的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Auto-Publishing and
- 下一篇: spring boot actuator