使用 Spring HATEOAS 开发 REST 服务
学习博客:https://www.ibm.com/developerworks/cn/java/j-lo-SpringHATEOAS/
http://www.cnblogs.com/coderland/p/5902997.html
Spring HATEOAS 是一个用于支持实现超文本驱动的 REST Web 服务的开发库。是 HATEOAS 的实现。
(HATEOAS背后的思想就是响应中包含指向其它资源的链接。客户端可以利用这些链接和服务器交互)
非HATEOAS的响应例子:
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的响应例子:
{ "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都包含了三部分:
href:用户可以用来检索资源或者改变应用状态的URI
rel:描述href指向的资源和现有资源的关系
method:和此URI需要的http方法
在rel中“self”表示了自描述的关系。如果一个资源包含其它资源,那么可以按照下面例子组织:
{ "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数组
一、首先了解Rest架构
(一)REST 架构风格:
1、Rest架构风格已经成为了构建 Web 服务时应该遵循的事实标准。
2、很多 Web 服务和 API 都宣称满足了 REST 架构风格的要求,即所谓的“RESTful”服务。
不过就如同其他很多流行的概念一样,不少人对于 REST 的含义还是存在或多或少的种种误解。
REST 在某些时候被当成了一种营销的手段。
不少所谓的“RESTful” Web 服务或 API 实际上并不满足 REST 架构风格的要求。
这其中的部分原因在于 REST 的含义比较复杂,包含很多不同方面的内容。
(二)REST 是 Representational state transfer ---表达性状态转换
1、REST 是 Representational state transfer 的缩写,翻译为表达性状态转换。
2、REST 是一种架构风格,它包含了一个分布式超文本系统中对于组件、连接器和数据的约束。
3、REST 是作为互联网自身架构的抽象而出现的,其关键在于所定义的架构上的各种约束。
只有满足这些约束,才能称之为符合 REST 架构风格。
【1】REST 的约束包括:
(1)客户端-服务器结构
通过一个统一的接口来分开客户端和服务器,使得两者可以独立开发和演化。
客户端的实现可以简化,而服务器可以更 容易的满足可伸缩性的要求。
(2)无状态
在不同的客户端请求之间,服务器并不保存客户端相关的上下文状态信息。
任何客户端发出的每个请求都包含了服务器处理该请求所需的全部信息。
(3)可缓存
客户端可以缓存服务器返回的响应结果。服务器可以定义响应结果的缓存设置。
(4)分层的系统
在分层的系统中,可能有中间服务器来处理安全策略和缓存等相关问题,以提高系统的可伸缩性。
客户端并不需要了解中间的这些层次的细节。
(5)按需代码(可选)
服务器可以通过传输可执行代码的方式来扩展或自定义客户端的行为。这是一个可选的约束。
(6)统一接口
该约束是 REST 服务的基础,是客户端和服务器之间的桥梁。该约束又包含下面 4 个子约束。
A、资源标识符:
每个资源都有各自的标识符。客户端在请求时需要指定该标识符。在 REST 服务中,该标识符通常是 URI。
客户端所获取的是资源的表达(representation),通常使用 XML 或 JSON 格式。
B、通过资源的表达来操纵资源
客户端根据所得到的资源的表达中包含的信息来了解如何操纵资源,比如对资源进行修改或删除。
C、自描述的消息
每条消息都包含足够的信息来描述如何处理该消息。
D、超媒体作为应用状态的引擎(HATEOAS)
客户端通过服务器提供的超媒体内容中动态提供的动作来进行状态转换。
==》表达性状态转换
“表达性”是指对于资源的操纵都是通过服务器提供的资源的表达来进行的。
客户端在根据资源的标识符获取到资源的表达之后,从资源的表达中可以发现其可以使用的动作。
使用这些动作会发出新的请求,从而触发状态转换。
二、HATEOAS 约束
HATEOAS(Hypermedia as the engine of application state)是 REST 架构风格中最复杂的约束,
也是构建成熟 REST 服务的核心。
它的重要性在于打破了客户端和服务器之间严格的契约,使得客户端可以更加智能和自适应,
而 REST 服务本身的演化和更新也变得更加容易。
(“hepermedia”表示任何包含指向图片、电影、文字等资源的链接,Web是超媒体的经典例子)
(一)首先了解REST成熟度模型
REST 成熟度模型把 REST 服务按照成熟度划分成 4 个层次(成熟度由低-》高):
1、第一层次(Level 0)的 Web 服务只是使用 HTTP 作为传输方式,实际上只是远程方法调用(RPC)的一种具体形式。
SOAP 和 XML-RPC 都属于此类
2、第二层次(Level 1)的 Web 服务引入了资源的概念。每个资源有对应的标识符和表达
3、第三层次(Level 2)的 Web 服务使用不同的 HTTP 方法来进行不同的操作,并且使用 HTTP 状态码来表示不同的结果。
如:HTTP GET 方法来获取资源,HTTP DELETE 方法来删除资源
4、第四层次(Level 3)的 Web 服务使用 HATEOAS。在资源的表达中包含了链接信息。
客户端可根据链接来发现可执行的动作
注意:
【1】不使用 HATEOAS 的 REST 服务:
客户端和服务器的实现之间是紧密耦合的。客户端需要根据服务器提供的相关文档来了解所暴露的资源和对应的操作。当服务器发生了变化时,如修改了资源的 URI,客户端也需要进行相应的修改。
【2】使用 HATEOAS 的 REST 服务:
客户端可通过服务器提供的资源的表达来智能地发现可以执行的操作。当服务器发生了变化时,客户端并不需要做出修改,因为资源的 URI 和其他信息都是动态发现的。
三、待办事项示例--说明HATEOAS
【业务场景】:用户可创建新待办事项、进行编辑或标记为已完成。(张三的待办事项列表中罗列具体待办事项)
【示例资源】:1.用户(应用中的用户) 2.列表(待办事项的列表,属于某个用户) 3.事项(具体的待办事项,属于某个列表)
【业务实现】:应用提供相关的 REST 服务来完成对于列表和事项两个资源的 CRUD 操作
【应用技术】:Spring HATEOAS
如果 Web 应用基于 Spring 框架开发,那么可以直接使用 Spring 框架的子项目 HATEOAS 来开发满足 HATEOAS 约束的 Web 服务。本文的示例应用基于 Java 8 和使用 Spring Boot 1.1.9 来创建,Spring HATEOAS 的版本是 0.16.0.RELEASE。
Step1:基本配置
【1】满足 HATEOAS 约束的 REST 服务最大的特点:
服务器提供给客户端的表达中包含了动态的链接信息,客户端通过这些链接来发现可以触发状态转换的动作
【2】为何应用Spring HATEOAS
Spring HATEOAS 的主要功能在于提供了简单的机制来创建这些链接,并与 Spring MVC 框架有很好的集成。对于已有的 Spring MVC 应用,只需要一些简单改动就可满足 HATEOAS 约束。对于一个 Maven 项目来说,只需要添加如下依赖即可。
<!-- Spring HATEOAS 的 Maven 依赖声明--> <dependency> <groupId>org.springframework.hateoas</groupId> <artifactId>spring-hateoas</artifactId> <version>0.16.0.RELEASE</version> </dependency>
Step2:资源
REST 架构中的核心概念之一是资源。服务器提供的是资源的表达,通常使用 JSON 或 XML 格式。在一般的 Web 应用中,服务器端代码会对所使用的资源建模,提供相应的模型层 Java 类,这些模型层 Java 类通常包含 JPA 相关的注解来完成持久化。在客户端请求时,服务器端代码通过 Jackson 或 JAXB 把模型对象转换成 JSON 或 XML 格式。如【代码1】:
/** 代码1:表示列表的模型类 List 的声明。【1个用户-》N个事项列表,1个事项列表-》N个具体事项】*/ @Entity public class List extends AbstractEntity { private String name; @ManyToOne @JsonIgnore private User user; @OneToMany(mappedBy = "list", fetch = FetchType.LAZY) @JsonIgnore private 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; } }
当客户端请求某个具体的 List 类的对象时,服务器端返回JSON 格式的表达。如【代码2】
/** 代码2:List 类的对象的 JSON 格式的表达*/ { "id": 1, "name": "Default" }
在【代码2】中,服务器端返回的只是模型类对象本身的内容,并没有提供相关的链接信息。为了把模型对象类转换成满足 HATEOAS 要求的资源,需要添加链接信息。
A、Spring HATEOAS 使用 org.springframework.hateoas.Link 类来表示链接。
B、Link 类遵循 Atom 规范中对于链接的定义,包含 rel 和 href 两个属性。
a、属性 rel 表示的是链接所表示的关系(relationship)
b、href 表示的是链接指向的资源标识符,一般是 URI。
C、资源通常都包含一个属性 rel 值为 self 的链接,用来指向该资源本身。
在创建资源类时,可以继承自 Spring HATEOAS 提供的 org.springframework.hateoas.Resource 类,Resource 类提供了简单的方式来创建链接。如【代码3】
/** 代码3:封装方式一==》模型类 List 对应的资源类 ListResource 的声明*/ public class ListResource extends Resource { private final List 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; } }
如【代码3】所示,ListResource 类继承自 Resource 类并对 List 类的对象进行了封装,添加了两个链接。在使用 ListResource 类之后,服务器端返回的表达格式如代码【4】所示。
/** 代码4:使用 ListResource 类之后的 JSON 格式的表达*/ { "list": { "id": 1, "name": "Default" }, "links": [ { "rel": "self", "href": "http://localhost:8080/lists/1" }, { "rel": "items", "href": "http://localhost:8080/lists/1/items" } ] }
代码【4】的 JSON 内容中添加了额外的 links 属性,并包含了两个链接。不过模型类对象的内容被封装在属性 list 中。这是因为 ListResource 类直接封装了整个的 List 类的对象,而不是把 List 类的属性提取到 ListResource 类中。如果需要改变输出的 JSON 表达的格式,可以使用另外一种封装方式的 ListResource 类,如代码【5】所示。
/** 代码5:封装方式二=》不同封装格式的 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; } }
/** 代码6:使用不同封装方式的 JSON 格式的表达*/ { "id": 1, "name": "Default", "links": [ { "rel": "self", "href": "http://localhost:8080/lists/1" }, { "rel": "items", "href": "http://localhost:8080/lists/1/items" } ] }
对比封装方式1[代码3]&封装方式2[代码5]之间的差异:
两种不同封装方式各有优缺点:
方式1的优点实现起来很简单,只需要把模型层的对象直接包装即可;
方式2虽然实现起来相对比较复杂,但是可以对资源的表达格式进行定制,使得资源的表达格式更直接。
在代码实现中经常会需要把模型类对象转换成对应的资源对象,如把 List 类的对象转换成 ListResource 类的对象。一般的做法是通过“new ListResource(list)”这样的方式来进行转换。可以使用 Spring HATEOAS 提供的资源组装器把转换的逻辑封装起来。资源组装器还可以自动创建 rel 属性为 self 的链接。代码【7】中给出了组装资源类 ListResource 的 ListResourceAssembler 类的实现。
/** 代码7:组装资源类 ListResource 的 ListResourceAssembler 类的实现*/
/** ResourceAssemblerSupport类的默认实现是通过反射来创建资源对象的*/ public class ListResourceAssembler extends ResourceAssemblerSupport<List, ListResource> { public ListResourceAssembler() {
/** 指定使用资源的Spring MVC控制器java类和资源java类
ListRestController类作用:创建 rel 属性为 self 的链接
*/ super(ListRestController.class, ListResource.class); }
/** toResource方法:用来完成实际的转换*/ @Override public ListResource toResource(List list) {
/** 使用了 ResourceAssemblerSupport 类的 createResourceWithId 方法来创建一个包含 self 链接的资源对象*/ ListResource resource = createResourceWithId(list.getId(), list); return resource; }
/** instantiateResource方法:用来根据一个模型类 List 的对象创建出 ListResource 对象
*/ @Override protected ListResource instantiateResource(List entity) { return new ListResource(entity); } }
说明:
(1)在创建 ListResourceAssembler 类的对象时需要指定使用资源的 Spring MVC 控制器 Java 类和资源 Java 类。
对于 ListResourceAssembler 类来说分别是 ListRestController 和 ListResource。
=》ListRestController 类其作用是用来创建 rel 属性为 self 的链接。
(2)ListResourceAssembler 类的 instantiateResource 方法用来根据一个模型类 List 的对象创建出 ListResource 对象。
(3)ResourceAssemblerSupport 类的默认实现是通过反射来创建资源对象的。
(4)toResource 方法用来完成实际的转换。此处使用了 ResourceAssemblerSupport 类的 createResourceWithId 方法来创建一个包含 self 链接的资源对象。在代码中需要创建 ListResource 的地方,都可以换成使用 ListResourceAssembler,如代码【8】
/** 代码[8]:使用 ListResourceAssembler 的示例*/ //组装单个资源对象 toResources 方法是 ResourceAssemblerSupport 类提供的 new ListResourceAssembler().toResource(list); //组装资源对象的集合 toResources 方法是 ResourceAssemblerSupport 类提供的 new ListResourceAssembler().toResources(lists);
三、链接
HATEOAS 的核心是链接。链接的存在使得客户端可以动态发现其所能执行的动作。
链接由 rel 和 href 两个属性组成。
【1】属性 rel 表明了该链接所代表的关系含义
应用可以根据需要为链接选择最适合的 rel 属性值。
由于每个应用的情况并不相同,对于应用相关的 rel 属性值并没有统一的规范。
不过对于很多常见的链接关系,IANA 定义了规范的 rel 属性值。
如果在应用中使用自定义 rel 属性值,一般的做法是属性值全部为小写,中间使用“-”分隔。
【2】属性 href 表示的是资源的标识符
对于 Web 应用来说,通常是一个 URL。URL 必须指向的是一个绝对的地址。
在应用中创建链接时,在 URL 中使用硬编码的主机名和端口号显然不是好的选择。
Spring MVC 提供了相关的工具类可以获取 Web 应用启动时的主机名和端口号,不过创建动态的链接 URL 还需要可以获取资源的访问路径。
对于一个典型的 Spring MVC 控制器来说,其声明如代码【9】所示。
/**代码[9]:Spring MVC 控制器 ListRestController 类的实现*/ @RestController @RequestMapping("/lists") public class ListRestController { @Autowired private 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)); } }
Spring MVC 控制器 ListRestController 类通过“@RequestMapping”注解声明了其访问路径是“/lists”,
而访问单个资源的路径是类似“/lists/1”这样的形式。
在创建资源的链接时,指向单个资源的链接的 href 属性值是类似“http://localhost:8080/lists/1”这样的格式。
而其中的“/lists”不应该是硬编码的,否则当修改了 ListRestController 类的“@RequestMapping”时,所有相关的生成链接的代码都需要进行修改。
Spring HATEOAS 提供了 org.springframework.hateoas.mvc.ControllerLinkBuilder 来解决这个问题,
用来根据 Spring MVC 控制器动态生成链接。代码【10】给出了创建单个资源的链接的方式。
/** 代码[10]:使用 ControllerLinkBuilder 类创建链接*/ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; Link link = linkTo(ListRestController.class).slash(listId).withSelfRel();
通过 ControllerLinkBuilder 类的 linkTo 方法,先指定 Spring MVC 控制器的 Java 类,再通过 slash 方法来找到下一级的路径,最后生成属性值为 self 的链接。在使用 ControllerLinkBuilder 生成链接时,除了可以使用控制器的 Java 类之外,还可以使用控制器 Java 类中包含的方法。如代码【11】所示。
/** 代码[11]通过控制器 Java 类中的方法生成链接*/ Link link = linkTo(methodOn(ItemRestController.class).readItems(listId)).withRel("items");
链接使用的是 ItemRestController 类中的 readItems 方法。
参数 listId 是组成 URI 的一部分,在调用 readItems 方法时需要提供。
上面介绍的是通过 Spring MVC 控制器来创建链接,另外一种做法是从模型类中创建。这是因为控制器通常用来暴露某个模型类。如 ListRestController 类直接暴露模型类 List,并提供了访问 List 资源集合和单个 List 资源的接口。对于这样的情况,并不需要通过控制器来创建相关的链接,而可以使用 EntityLinks。
首先需要在控制器类中通过“@ExposesResourceFor”注解声明其所暴露的模型类,如代码【12】中 ListRestController 类的声明。
/** 代码[12]“@ExposesResourceFor”注解的使用*/ @RestController @ExposesResourceFor(List.class) @RequestMapping("/lists") public class ListRestController { }
另:在 Spring 应用的配置类中需通过“@EnableEntityLinks”注解来启用 EntityLinks 功能。需添加代码【13】Maven 依赖。
/** 代码[13]EntityLinks 功能所需的 Maven 依赖*/ <dependency> <groupId>org.springframework.plugin</groupId> <artifactId>spring-plugin-core</artifactId> <version>1.1.0.RELEASE</version> </dependency>
在需要创建链接的代码中,只需要通过依赖注入的方式添加对 EntityLinks 的引用,就可以使用 linkForSingleResource 方法来创建指向单个资源的链接,如代码【14】所示。
/**代码14:使用 EntityLinks 创建链接 */ @Autowired private EntityLinks entityLinks; entityLinks.linkForSingleResource(List.class, 1)
需要注意的是,为了 linkForSingleResource 方法可以正常工作,控制器类中需要包含访问单个资源的方法,而且其“@RequestMapping”是类似“/{id}”这样的形式。
四、超媒体控制与 HAL
在添加了链接之后,服务器端提供的表达可以帮助客户端更好的发现服务器端所支持的动作。
在具体的表达中,应用虽然可以根据需要选择最适合的格式,但是在表达的基本结构上应该遵循一定的规范,
这样可以保证最大程度的适用性。这个基本结构主要是整体的组织方式和链接的格式。
首先介绍JSON Hypermedia Types
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(Hypertxt Application Language)是简单的超媒体类型,一个被广泛采用的超文本表达的规范。
HAL同时支持XML和JSON格式
应用可以考虑遵循该规范,Spring HATEOAS 提供了对 HAL 的支持。
HAL媒体类型定义了一种资源,它是状态的容器、links的集合、嵌套资源的集合。如下图所示
资源状态是用JSON的key/value形式表达的。如下面所示:
{ "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" } } }
HAL 规范:
HAL 规范本身是很简单的,代码【15】给出了示例的 JSON 格式的表达。
/** HAL 规范的示例 JSON 格式的表达*/ { "_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 规范围绕资源和链接这两个简单的概念展开。
(1)资源的表达中包含链接、嵌套的资源和状态。资源的状态是该资源本身所包含的数据。
(2)链接则包含其指向的目标(URI)、所表示的关系和其他可选的相关属性。
【1】对应到 JSON 格式中
A。资源的链接包含在_links 属性对应的哈希对象中。
该_links 哈希对象中的键(key)是链接的关系,
而值(value)则是另外一个包含了 href 等其他链接属性的对象或对象数组。
B。当前资源中所包含的嵌套资源由_embeded 属性来表示,其值是一个包含了其他资源的哈希对象。
链接的关系不仅是区分不同链接的标识符,同样也是指向相关文档的 URL。
文档用来告诉客户端如何对该链接所指向的资源进行操作。
当开发人员获取到了资源的表达之后,可以通过查看链接指向的文档来了解如何操作该资源。
【2】使用 URL 作为链接的关系带来的问题:
URL 作为属性名称来说显得过长,而且不同关系的 URL 的大部分内容是重复的。
为了解决这个问题,可以使用 Curie。Curie 可以作为链接关系 URL 的模板。
链接的关系声明时使用 Curie 的名称作为前缀,不用提供完整的 URL。
应用中声明的 Curie 出现在_links 属性中。
代码中定义了 URI 模板为“http://www.midgetontoes.com/todolist/rels/{rel}”的名为 todo 的 Curie。
在使用了 Curie 之后,名为 items 的链接关系变成了包含前缀的“todo:items”的形式。
这就表示该链接的关系实际上是“http://www.midgetontoes.com/todolist/rels/items”。
Spring HATEOAS 的 HAL 支持
目前 Spring HATEOAS 仅支持 HAL 一种超媒体表达格式
只需在应用的配置类上添加“@EnableHypermediaSupport(type= {HypermediaType.HAL})”注解就可启用该超媒体支持。
在启用了超媒体支持之后,服务器端输出的表达格式会遵循 HAL 规范。
另外,启用超媒体支持会默认启用“@EnableEntityLinks”。
在启用超媒体支持之后,应用需要进行相关的定制使得生成的 HAL 表达更加友好。
首先是内嵌资源在_embedded 对应的哈希对象中的属性值,
该属性值是由 org.springframework.hateoas.RelProvider 接口的实现来提供的。
对于应用来说,只需要在内嵌资源对应的模型类中添加 org.springframework.hateoas.core.Relation 注解即可,
/** 代码16:在模型类中添加 @Relation 注解*/ @Relation(value = "list", collectionRelation = "lists") public class List extends AbstractEntity { }
声明了当模型类 List 的对象作为内嵌资源时,单个资源使用 list 作为属性值,多个资源使用 lists 作为属性值。
如果需要添加 Curie,则提供 org.springframework.hateoas.hal.CurieProvider 接口的实现,如代码【17】。
利用已有的 org.springframework.hateoas.hal.DefaultCurieProvider 类并提供 Curie 的前缀和 URI 模板即可。
/** 代码17:添加 CurieProvider 接口的实现*/ @Bean public CurieProvider curieProvider() { return new DefaultCurieProvider("todo", new UriTemplate("http://www.midgetontoes.com/todolist/rels/{rel}")); }
示例2:HATEOAS in Spring
<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")); } }
下图是上面例子的响应:
总结:
在开发一个新的 Web 服务或 API 时,REST 架构风格已经成为事实上的标准。在开发时需要明白 REST 架构风格中所包含的约束的含义。HATEOAS 作为 REST 服务约束中最复杂的一个,目前还没有得到广泛的使用。但是采用 HATEOAS 所带来的好处是很大的,可以帮助客户端和服务器更好的解耦,可以减少很多潜在的问题。Spring HATEOAS 在 Spring MVC 框架的基础上,允许开发人员通过简单的配置来添加 HATEOAS 约束。如果应用本身已经使用了 Spring MVC,则同时启用 HATEOAS 是一个很好的选择。