Spring Boot HATEOAS的基本使用
引言:
HATEOAS(Hypermedia as the Engine of Application State):超媒体作为应用状态引擎。是一种创建自描述API的方式。在API做返回的资源中会包含相关资源的链接, 客户端只需要了解最少的 API URL信息就能导航整个API。
一个具有注脚的文本。1
maven 依赖项:
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
<version>1.2.2</version>
</dependency>
注意:自 Spring HATEOAS 1.0 开始, 有以下变化:
ResourceSupport
类转变为RepresentationModel
类Resource
类转变为EntityModel
类Resources
类转变为CollectionModel
类PagedResources
类转变为PagedModel
类ResourceAssembler
类 转变为RepresentationModelAssembler
类; 同时,toResource()
方法转变为toModel()
、toResources()
转变为toCollectionModel()
。2
基本使用:
1. 定义一个基本的用户类:
import lombok.Data;
import javax.persistence.*;
*
* 定义的基本用户类, 使用lombok的 @Data 注解
* 自动生成对应属性的Getter和Setter方法
*/
@Data
@Entity // 这是一个数据库表对应的实体对象类
@Table(name = "user_info") // 对应的数据哭表名
public class UserInfo {
@Id
@Column(name = "user_id")
// 用户的 Id 属性
private Long userId;
@Basic
@Column(name = "user_name")
// 用户名
private String userName;
@Basic
@Column(name = "user_gender")
// 用户性别
private String userGender;
}
- 定一个用户资源类, 这个类应当继承
RepresentationModel<T>
类, 从而包含对应的资源超链接信息。
import com.example.hateoas.Entity.UserInfo;
import lombok.Getter;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.server.core.Relation;
/**
* 定义的用户信息资源类, 包含所需要展现的属性以及从 RepresentationModel
* 类继承的 Link 等链接相关的属性
*/
/*
@Relation: 消除 JSON 字段名与 Java 类属性之间的耦合
value: 在JSON中引用一个单体对象时的单体对象名称
collectionRelation: 当有多个对象时, 将其值设置为对应的JSON字段名
*/
@Relation(value = "userInfo", collectionRelation = "userInfos")
public class UserInfoResource extends RepresentationModel<UserInfoResource> {
@Getter
// 用户名属性
private final String userName;
@Getter
// 用户性别
private final String userGender;
/*
不含超链接属性的构造函数
*/
public UserInfoResource(UserInfo userInfo) {
this.userName = userInfo.getUserName();
this.userGender = userInfo.getUserGender();
}
/*
添加对应的链接对象的构造函数,
因此能够在创建对象时就可以添加想对应的超链接
*/
public UserInfoResource(UserInfo userInfo, Link link) {
// 调用父类构造函数添加对应链接
super(link);
this.userName = userInfo.getUserName();
this.userGender = userInfo.getUserGender();
}
}
- 使用 JPA 定义 UserInfo 类的数据访问接口
import com.example.hateoas.Entity.UserInfo;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 定义的用户数据库访问接口, Spring 的 JPA 可以帮助我们完成基本的数据库操作。
* 通过继承 CrudRepository<T, ID> 接口, Spring将会自动生成数据库访问对象,
* 而我们只需要按照它的词法结构定义接口函数便可完成对数据库的操作
*/
@Repository
public interface UserInfoRepository extends CrudRepository<UserInfo, Long> {
/*
通过 Pageable 对象访问数据库, 从而实现分页的效果
*/
List<UserInfo> findAll(@Param("page") Pageable page);
/*
通过 userId 属性来查找对应 UserInfo 对象
*/
UserInfo findUserInfoByUserId(@Param("userId") Long userId);
}
- 现在, 定义一个控制器。
import com.example.hateoas.Entity.UserInfo;
import com.example.hateoas.Repository.UserInfoRepository;
import com.example.hateoas.Resource.UserInfoResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
/**
* 用户信息控制器类, 处理相关的请求
*/
@RestController // 声明这是一个 REST 风格的控制器
@RequestMapping(path = "/user") // 该控制器的基础访问路径
public class UserInfoController {
// 通过构造函数注入 UserInfoRepository 接口对象, 实现对数据库的基本访问
private final UserInfoRepository userInfoRepository;
@Autowired
public UserInfoController(UserInfoRepository userInfoRepository) {
this.userInfoRepository = userInfoRepository;
}
@GetMapping(path = "/userInfos")
public CollectionModel<UserInfoResource>
userInfoCollection() {
// 通过Pageable对象查找前三个 UserInfo 对象
List<UserInfo> userInfoList = userInfoRepository
.findAll(PageRequest.of(0, 3));
/*
定义的UserInfoResource对象列表,通过遍历 UserInfo 列表,
添加相关的超链接属性
*/
List<UserInfoResource> resources = new ArrayList<>();
for (UserInfo info: userInfoList) {
UserInfoResource resource = new UserInfoResource(info);
/*
WebMvcLinkBuilder.linkTo 链接到对应的地址,
在这里使用 WebMvcLinkBuilder.methodOn() 方法, 可以访问到对象的方法名,
从而得到方法对应的地址, 避免硬编码。
当然, 也可以使用 linkTo(***Controller.class).slash()
方法来设置路径, 但是这样的话会导致硬编码
withRel:这个链接对应的链接名称, 这里定义为 Info1Address
*/
resource.add(linkTo(methodOn(UserInfoController.class)
.infoResource(info.getUserId())).withRel("infoAddress"));
resources.add(resource);
}
// 将得到的 UserInfoResource 包装到资源集合中
CollectionModel<UserInfoResource> models
= CollectionModel.of(resources);
// 这个资源集合的访问超链接
models.add(linkTo(methodOn(UserInfoController.class).userInfoCollection())
.withRel("userInfos"));
return models;
}
/**
* 通过 userId 查找对应的 UserInfo 对象
* @param userId : 查找的 userId
* @return : 查找到的 UserInfo 对象
*/
@GetMapping(path = "/userInfo/{userId}")
public EntityModel<UserInfoResource> infoResource(
@PathVariable(name = "userId") Long userId) {
UserInfo userInfo = userInfoRepository.findUserInfoByUserId(userId);
return new EntityModel<>(new UserInfoResource(userInfo));
}
}
- 现在,运行该程序, 打开浏览器, 在地址栏内输入 http://127.0.0.1:8080/userInfos, 或者在命令行内输入
curl http://127.0.0.1:8080/userInfos
将会看到类似的输出:
- 每次都添加超链接可能会有些麻烦, 使用
RepresentationModelAssembler
接口会减少一些工作量,定义一个 assembler 类实现这个接口
import com.example.hateoas.Controller.UserInfoController;
import com.example.hateoas.Entity.UserInfo;
import com.example.hateoas.Resource.UserInfoResource;
import org.springframework.hateoas.server.RepresentationModelAssembler;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
/**
* 定义一个 Assembler 类, 实现 RepresentationModelAssembler 接口,
* 可以避免每次都需要添加对应的超链接
*/
public class UserInfoAssembler implements
RepresentationModelAssembler<UserInfo, UserInfoResource> {
@Override
public UserInfoResource toModel(UserInfo userInfo) {
return new UserInfoResource(userInfo,
linkTo(methodOn(UserInfoController.class)
.infoResource(userInfo.getUserId()))
.withRel("infoAddress"));
}
}
- 现在, 使用 assembler 类完成之前的工作。
/**
* 使用 Assembler 对象转变为对应的集合
* @return : 转变后的对象集合
*/
@GetMapping(path = "/userAssembler")
public CollectionModel<UserInfoResource>
userAssembler(){
// 通过Pageable对象查找前三个 UserInfo 对象
List<UserInfo> userInfoList = userInfoRepository
.findAll(PageRequest.of(0, 3));
// 将得到的 UserInfo 对象列表转变为对应的集合
CollectionModel<UserInfoResource> models
= new UserInfoAssembler().toCollectionModel(userInfoList);
// 添加当前集合的超链接
models.add(linkTo(methodOn(UserInfoController.class)
.userAssembler()).withRel("userAssembler"));
return models;
}
- 现在,再次运行该程序, 再次打开浏览器, 在地址栏内输入 http://127.0.0.1:8080/userInfos, 或者在命令行内输入
curl http://127.0.0.1:8080/userInfos
将会看到类似的输出:
项目地址:https://github.com/LiuXianghai-coder/Spring-Study/tree/master/spring-hateoas
《Spring 实战(第五半)》克雷格·沃斯(CraigWalls)著 张卫滨 译 P.145 6.2启用超媒体 ↩︎
https://docs.spring.io/spring-hateoas/docs/current/reference/html/#reference ↩︎