前言
超媒体是 REST 的一个重要方面。它让你构建服务,在很大程度上解耦客户端和服务器,让它们独立发展。为 REST 资源返回的表示不仅包含数据,还包含指向相关资源的链接。因此,表示的设计对于整体服务的设计至关重要。服务处理GET请求时,Json响应如下
{
"_embedded": {
"ingredients": [
{
"name": "面皮",
"type": "necessities",
"_links": {
"self": {
"href": "http://localhost:8082/query/1"
}
}]
},
"_links": {
"recent": {
"href": "http://localhost:8082/test/recent"
}
}
}
响应已经表明您可以使用name查询字符串中的可选参数自定义问候语,
这种风格的HATEOAS被称为HAL,是一种在JSON响应中嵌套了超链接的简单的格式,"_links"属性为用户提供导航API的超链接。
1.添加超链接
spring HATEOAS项目为Spring 提供了超链接的支持。添加相关依赖,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
Spring HATEOAS提供了两个主要的类型来表示超链接资源:EntityModel和CollectionModel。EntityModel代表一个资源,CollectionModel代表资源的集合。
首先第一步为资源添加超链接,
@GetMapping("/recent")
public CollectionModel<IngredientsResource> halIngredients(){
//在前几章中写过findAll(),查询数据库中所有的Ingredients,以列表形式返回
List<Ingredients> ingredientsList = ingredientsRepository.findAll();
CollectionModel<EntityModel<Ingredients>> collectionModel = CollectionModel.wrap(ingredientsList);
collectionModel.add(
new Link("http://localhost:8082/query/recent","recent"));
return collectionModel;
}
使用CollectionModel.wrap()将IngredientsList包装成 CollectionModel<EntityModel>,并且在资源的集合添加了名为recent的关联关系,这样请求API时,JSON就会包含以下片段
"_links": {
"recent": {
"href": "http://localhost:8082/test/recent"
}
}
但只是为资源的集合添加了超链接,并没有将每一个元素添加相关的超链接。
所以要进行第二步:
2.创建资源装配器
1.创建资源类型
//携带链接的资源类型
public class IngredientsResource extends RepresentationModel<IngredientsResource> {
@Getter
private final String name;
@Getter
private final String type;
public IngredientsResource(Ingredients ingredients) {
this.name = ingredients.getName();
this.type = ingredients.getType();
}
}
这个资源类型和ingredients领域类型没有太多区别,就是没有包含ingredients的id属性。这是因为必要在API中暴露数据库相关的API,对于客户端来说,资源的self链接会作为该资源的标识符。有一个简单的构造器,可以将ingredients对象转换成IngredientsResource 对象。
为了将ingredients对象转换成IngredientsResource 对象,2.需要创建一个资源装配器
public class IngredientsResourceAssembler extends
RepresentationModelAssemblerSupport<Ingredients,IngredientsResource> {
public IngredientsResourceAssembler(Class<?> controllerClass, Class<IngredientsResource> resourceType) {
super(controllerClass, resourceType);
}
@Override
protected IngredientsResource instantiateModel(Ingredients ingredients) {
return new IngredientsResource(ingredients);//只是为了实列化 EntityModel对象
}
@Override
public IngredientsResource toModel(Ingredients entity) {
return createModelWithId(entity.getId(),entity);//不仅创建EntityModel对象,还为其填充链接。
//设置一个self链接,这个链接的URL是根据对象的id来衍生出来的
}
}
3.最后可以调整一下halIngredients(),让它使用IngredientsResourceAssembler 。
@GetMapping("/recent")
public CollectionModel<IngredientsResource> halIngredients(){
List<Ingredients> ingredientsList = ingredientsRepository.findAll();
CollectionModel<IngredientsResource> collectionModel =
new IngredientsResourceAssembler(HateoasController.class, IngredientsResource.class).toCollectionModel(ingredientsList);
collectionModel.add(
new Link("http://localhost:8082/query/recent","recent"));
return collectionModel;
}
最后JSON结果如下:
{
"_embedded": {
"ingredients": [
{
"name": "面皮",
"type": "necessities",
"_links": {
"self": {
"href": "http://localhost:8082/query/1"
}
}
},
{
"name": "鸡肉",
"type": "meat",
"_links": {
"self": {
"href": "http://localhost:8082/query/2"
}
}
},
{
"name": "玉米",
"type": "vegetables",
"_links": {
"self": {
"href": "http://localhost:8082/query/3"
}
}
},
{
"name": "醋",
"type": "seasoning",
"_links": {
"self": {
"href": "http://localhost:8082/query/4"
}
}
},
{
"name": "虾蟹",
"type": "meat",
"_links": {
"self": {
"href": "http://localhost:8082/query/5"
}
}
},
{
"name": "酸汤",
"type": "seasoning",
"_links": {
"self": {
"href": "http://localhost:8082/query/6"
}
}
},
{
"name": "油",
"type": "seasoning",
"_links": {
"self": {
"href": "http://localhost:8082/query/7"
}
}
},
{
"name": "猪肉",
"type": "meat",
"_links": {
"self": {
"href": "http://localhost:8082/query/8"
}
}
},
{
"name": "白菜",
"type": "vegetables",
"_links": {
"self": {
"href": "http://localhost:8082/query/9"
}
}
}
]
},
"_links": {
"recent": {
"href": "http://localhost:8082/test/recent"
}
}
}
相关资料:Spring 官方文档
将Spring实战第5版中HATEOAS部分代码迁移到Spring HATEOAS 1.0