HATEOAS(The Hypermedia As The Engine Of Application Statue)是REST架構的主要約束。「hepermedia」表示任何包含指向圖片、電影、文字等資源的連接,Web是超媒體的經典例子。HATEOAS背後的思想其實很是簡單,就是響應中包含指向其它資源的連接。客戶端能夠利用這些連接和服務器交互。html
client不用事先知道服務或者工做流中不一樣步驟,還有client不用再爲不一樣的資源硬編碼URI了。並且服務器還能夠在不破壞和客戶端交互的狀況下,更改URI。git
非HATEOAS的響應例子是:github
GET /posts/1 HTTP/1.1 Connection: keep-alive Host: blog.example.com { "id" : 1, "body" : "My first blog post", "postdate" : "2015-05-30T21:41:12.650Z" }
而HATEOAS的響應例子則是:spring
{ "id" : 1, "body" : "My first blog post", "postdate" : "2015-05-30T21:41:12.650Z", "links" : [ { "rel" : "self", "href" : http://blog.example.com/posts/1, "method" : "GET" } ] }
上面的例子中,每個在links中的link都包含了三部分:json
href:用戶能夠用來檢索資源或者改變應用狀態的URI rel:描述href指向的資源和現有資源的關係 method:和此URI須要的http方法
在rel中「self」表示了自描述的關係。若是一個資源包含其它資源,那麼能夠按照下面例子組織:api
{ "id" : 1, "body" : "My first blog post", "postdate" : "2015-05-30T21:41:12.650Z", "self" : "http://blog.example.com/posts/1", "author" : "http://blog.example.com/profile/12345", "comments" : "http://blog.example.com/posts/1/comments", "tags" : "http://blog.example.com/posts/1/tags" }
上面的例子和前一個例子有些不一樣,沒有使用links數組。數組
JSON媒體類型沒有提供原生的超連接語法,因此爲了解決這個問題,有幾種JSON超媒體類型被建立出來:服務器
• HAL—http://stateless.co/hal_specification.html • JSON-LD—http://json-ld.org • Collection+JSON—http://amundsen.com/media-types/collection/ • JSON API—http://jsonapi.org/ • Siren—https://github.com/kevinswiber/siren
HAL是其中最流行的一種,並且被Spring Framework支持。架構
HAL(The Hypertext Application Language)是簡單的超媒體類型,由Mike Kelly於2011建立。它同時支持XML和JSON格式。HAL媒體類型定義了一種資源,它是狀態的容器、links的集合、嵌套資源的集合。以下圖所示:mvc
資源狀態是用JSON的key/valude形式表達的。以下面所示:
{ "id" : 1, "body" : "My first blog post", "postdate" : "2015-05-30T21:41:12.650Z" }
HAL規範中定義,使用_links包含全部的link。以下面例子所示:
{ "id" : 1, "body" : "My first blog post", "postdate" : "2015-05-30T21:41:12.650Z", "_links" : { "self": { "href": "http://blog.example.com/posts/1" }, "comments": { "href": "http://blog.example.com/posts/1/comments", "totalcount" : 20 }, "tags": { "href": "http://blog.example.com/posts/1/tags" } } }
在HAL嵌套資源的狀況,以下面例子所示:
{ "id" : 1, "body" : "My first blog post", "postdate" : "2015-05-30T21:41:12.650Z", "_links" : { "self": { "href": "http://blog.example.com/posts/1" }, "comments": { "href": "http://blog.example.com/posts/1/comments", "totalcount" : 20 }, "tags": { "href": "http://blog.example.com/posts/1/tags" } }, "_embedded" : { "author" : { "_links" : { "self": { "href": "http://blog.example.com/profile/12345" } }, "id" : 12345, "name" : "John Doe", "displayName" : "JDoe" } } }
<dependency> <groupId>org.springframework.hateoas</groupId> <artifactId>spring-hateoas</artifactId> <version>0.17.0.RELEASE</version> </dependency>
爲了簡化超連接的嵌入,Spring HATEOAS提供了org. springframework.hateoas.ResourceSupport,通常應由資源類進行擴展。ResourceSupport類爲增長/刪除連接提供了重載方法,它也包含了getId方法,此方法返回和資源相關的URI。getId的實現依據了REST的一個準則:一個資源的ID就是它的URI。
下面的例子是在Spring中使用HATEOAS的代碼:
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; @RestController public class PollController { @RequestMapping(value="/polls", method=RequestMethod.GET) public ResponseEntity<Iterable<Poll>> getAllPolls() { Iterable<Poll> allPolls = pollRepository.findAll(); for(Poll p : allPolls) { updatePollResourceWithLinks(p); return new ResponseEntity<>(allPolls, HttpStatus.OK); } } @RequestMapping(value="/polls/{pollId}", method=RequestMethod.GET) public ResponseEntity<?> getPoll(@PathVariable Long pollId) { Poll p = pollRepository.findOne(pollId); updatePollResourceWithLinks(p); return new ResponseEntity<> (p, HttpStatus.OK); } private void updatePollResourceWithLinks(Poll poll) { poll.add(linkTo(methodOn(PollController.class).getAllPolls()).slash(poll.getPollId()).withSelfRel()); poll.add(linkTo(methodOn(VoteController.class).getAllVotes(poll.getPollId())).withRel("votes")); poll.add(linkTo(methodOn(ComputeResultController.class).computeResult(poll.getPollId())).withRel("compute-result")); } }
下圖是上面例子的響應: