REST 這個詞想必大家都並不陌生。
REST 架構
REST 是 Representational state transfer 的縮寫,翻譯過來的意思是表達性狀態轉換。REST 是一種架構風格,它包含了一個分佈式超文本系統中對於組件、連接器和數據的約束。其關鍵在於所定義的架構上的各種約束。只有滿足這些約束,才能稱之爲符合 REST 架構風格。REST 的約束包括:
- 客戶端-服務器結構。通過一個統一的接口來分開客戶端和服務器,使得兩者可以獨立開發和演化。客戶端的實現可以簡化,而服務器可以更容易的滿足可伸縮性的要求。
- 無狀態。在不同的客戶端請求之間,服務器並不保存客戶端相關的上下文狀態信息。任何客戶端發出的每個請求都包含了服務器處理該請求所需的全部信息。
- 可緩存。客戶端可以緩存服務器返回的響應結果。服務器可以定義響應結果的緩存設置。
- 分層的系統。在分層的系統中,可能有中間服務器來處理安全策略和緩存等相關問題,以提高系統的可伸縮性。客戶端並不需要了解中間的這些層次的細節。
- 按需代碼(可選)。服務器可以通過傳輸可執行代碼的方式來擴展或自定義客戶端的行爲。這是一個可選的約束。
- 統一接口。該約束是 REST 服務的基礎,是客戶端和服務器之間的橋樑。該約束又包含下面 4 個子約束。
- 資源標識符。每個資源都有各自的標識符。客戶端在請求時需要指定該標識符。在 REST 服務中,該標識符通常是 URI。客戶端所獲取的是資源的表達(representation),通常使用 XML 或 JSON 格式。
- 通過資源的表達來操縱資源。客戶端根據所得到的資源的表達中包含的信息來了解如何操縱資源,比如對資源進行修改或刪除。
- 自描述的消息。每條消息都包含足夠的信息來描述如何處理該消息。
- 超媒體作爲應用狀態的引擎(HATEOAS)。客戶端通過服務器提供的超媒體內容中動態提供的動作來進行狀態轉換。這也是本文所要介紹的內容。
HATEOAS 約束
HATEOAS(Hypermedia as the engine of application state)是 REST 架構風格中最複雜的約束,也是構建成熟 REST 服務的核心。
在介紹 HATEOAS 之前,先介紹一下 Richardson 提出的 REST 成熟度模型。該模型把 REST 服務按照成熟度劃分成 4 個層次:
- 第一個層次(Level 0)的 Web 服務只是使用 HTTP 作爲傳輸方式,實際上只是遠程方法調用(RPC)的一種具體形式。SOAP 和 XML-RPC 都屬於此類。
- 第二個層次(Level 1)的 Web 服務引入了資源的概念。每個資源有對應的標識符和表達。
- 第三個層次(Level 2)的 Web 服務使用不同的 HTTP 方法來進行不同的操作,並且使用 HTTP 狀態碼來表示不同的結果。如 HTTP GET 方法來獲取資源,HTTP DELETE 方法來刪除資源。
- 第四個層次(Level 3)的 Web 服務使用 HATEOAS。在資源的表達中包含了鏈接信息。客戶端可以根據鏈接來發現可以執行的動作。
從上述 REST 成熟度模型中可以看到,使用 HATEOAS 的 REST 服務是成熟度最高的,也是推薦的做法。對於不使用 HATEOAS 的 REST 服務,客戶端和服務器的實現之間是緊密耦合的。
Spring HATEOAS
如果 Web 應用基於 Spring 框架開發,那麼可以直接使用 Spring 框架的子項目 HATEOAS 來開發滿足 HATEOAS 約束的 Web 服務。Spring框架爲我們提供了一個極其簡單的HATEOAS實現。
本文使用的是Spring boot 1.5.9.RELEASE。
引入Spring HATEOAS模塊。
<dependency>
<groupId> org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
創建資源類。
public class Greeting extends ResourceSupport {
private String content;
// @JsonCreator
// public Greeting(@JsonProperty("content") String content) {
// this.content = content;
// add(new Link("http://localhost:8080/lists/1"));
// add(new Link("http://localhost:8080/lists/1/items", "items"));
// }
public Greeting(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
注意:資源類Greeting中不可以有id成員變量,因爲其集成的父類ResourceSupport中有一個getId()方法。
創建控制器類。
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.ExposesResourceFor;
import org.springframework.hateoas.Link;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.byit.backstage.entity.Greeting;
@RestController
@ExposesResourceFor(Greeting.class)
@RequestMapping(path = "/lists")
public class GreetingController {
private static final String TEMPLATE = "Hello, %s!";
@Autowired
private EntityLinks entityLinks;
@RequestMapping("/greeting")
public HttpEntity<Greeting> greeting(@RequestParam(value = "name", required = false, defaultValue = "World") String name) {
Greeting greeting = new Greeting(String.format(TEMPLATE, name));
greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());
greeting.add(new Link("http://localhost:8080/lists/1"));
greeting.add(new Link("http://localhost:8080/lists/1/items", "items"));
greeting.add(new Link("www.baidu.com"));
return new ResponseEntity<>(greeting, HttpStatus.OK);
}
@RequestMapping("/{id}")
public HttpEntity<Greeting> lists(@RequestParam(value = "name", required = false, defaultValue = "World") String name, @PathVariable(value = "id") String id) {
Greeting greeting = new Greeting(String.format(TEMPLATE, name));
// greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());
// greeting.add(linkTo(GreetingController.class).slash(id).withSelfRel());
// greeting.add(new Link("http://localhost:8080/lists/1/items", "items"));
greeting.add(entityLinks.linkForSingleResource(Greeting.class, 1).withSelfRel());
return new ResponseEntity<>(greeting, HttpStatus.OK);
}
}
Spring boot配置類。
@SpringBootApplication
@EnableEntityLinks
public class Application_8501 {
public static void main(String[] args) {
SpringApplication.run(Application_8501.class, args);
}
}
打開瀏覽器,在地址欄輸入URL。
http://localhost:8501/lists/greeting?name=wangziyan
查看結果。
{
"content" : "Hello, wangziyan!",
"_links" : {
"self" : [ {
"href" : "http://localhost:8501/lists/greeting?name=wangziyan"
}, {
"href" : "http://localhost:8080/lists/1"
}, {
"href" : "www.baidu.com"
} ],
"items" : {
"href" : "http://localhost:8080/lists/1/items"
}
}
}
是不是很簡單~參考文檔:
http://spring.io/guides/gs/rest-hateoas/