HATEOAS(Hypermedia as the engine of application state)是 REST 架構風格中最複雜的限制,也是建構成熟 REST 服務的核心。它的重要性在于打破了用戶端和伺服器之間嚴格的契約,使得用戶端可以更加智能和自适應,而 REST 服務本身的演化和更新也變得更加容易。在了解HATEOAS之前先來看看 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。在資源的表達中包含了連結資訊。用戶端可以根據連結來發現可以執行的動作。
HATEOAS 的核心是連結,由 rel 和 href 兩個屬性組成。連結的存在使得用戶端可以動态發現其所能執行的動作。其中屬性 rel 表明了該連結所代表的關系含義
self | 指向目前資源本身的連結的 rel 屬性。每個資源的表達中都應該包含此關系的連結。 |
---|---|
edit | 指向一個可以編輯目前資源的連結。 |
item | 如果目前資源表示的是一個集合,則用來指向該集合中的單個資源。 |
collection | 如果目前資源包含在某個集合中,則用來指向包含該資源的集合。 |
related | 指向一個與目前資源相關的資源。 |
rel 屬性值 | 描述 |
search | 指向一個可以搜尋目前資源及其相關資源的連結。 |
first、last、previous、next | 這幾個 rel 屬性值都有集合中的周遊相關,分别用來指向集合中的第一個、最後一個、上一個和下一個資源。 |
案例:
Maven依賴
<dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.22</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency> <!-- HATEOAS 的依賴 --> <!-- https://mvnrepository.com/artifact/org.springframework.hateoas/spring-hateoas --> <dependency> <groupId>org.springframework.hateoas</groupId> <artifactId>spring-hateoas</artifactId> <version>1.4.5</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.22</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.22</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> </dependencies>
config層中的springMvc
@Configuration @ComponentScan("edu.nf.demo") @EnableWebMvc public class SpringMvc { }
WebInitializer
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[0]; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvc.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
entity
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Integer uid; private String username; }
vo
@Data public class ResultVo<T> extends RepresentationModel { private Integer code; private T data; private String message; }
controller
@RestController @RequestMapping("/api/v1") public class Controller { @GetMapping("/users/{uid}") public ResultVo<User> getUser( @PathVariable("uid") int id){ User use = new User(id,"user1"); ResultVo<User> userResultVo = new ResultVo<>(); userResultVo.setCode(HttpStatus.OK.value()); userResultVo.setData(use); //添加HATEOAS //建立self的連接配接 userResultVo.add(Link.of("http://localhost:8080/api/v1/users/uid")); //建立controller的連接配接,通過這個連結來通路所有的使用者資訊 userResultVo.add(Link.of("http://localhost:8080/api/v1/users","controller")); return userResultVo; } @GetMapping("/users") public ResultVo<List<User>> listUser(){ User user = new User(1,"user1"); User user1 = new User(2,"ser2"); List<User> users = Arrays.asList(user, user1); ResultVo<List<User>> vo= new ResultVo<>(); vo.setCode(HttpStatus.OK.value()); vo.setData(users); //添加Hateoas連結 vo.add(Link.of("http://localhost:8080/api/v1/users")); //根據id,擷取某個使用者資訊的連結 vo.add(Link.of("http://localhost:8080/api/v1/users/uid","item")); //根據使用者名搜尋使用者資訊的連結 vo.add(Link.of("http://localhost:8080/api/v1/users/name","search")); return vo; } }
結果:
這是第一個get中的連結的成功顯示,還有一個就不一一示範了