写点什么

Spring Boot「01」构建 REST API

作者:Samson
  • 2022 年 10 月 08 日
    上海
  • 本文字数:2736 字

    阅读完需:约 9 分钟

Spring Boot「01」构建 REST API

01-如何理解 REST / RESTful

REST(REpresentational State Transfer,表征状态转移)是一种架构风格,由 Roy Fielding 首次在其博士论文《Architectural Styles and the Design of Network-based Software Architectures》中提出。遵循 REST 风格的 Web API 也称为是 REST API。


Web API (or Web Service) conforming to the REST architectural style is a REST API. [4]


与其他的架构风格一样,REST 有其特定的指导原则与实现约束。如果要称一个服务或者 API 接口是 RESTful,则该服务或接口需要满足 REST 的指导原则与实现约束。更多关于 REST 指导原则的信息,感兴趣的读者可以参考[4]。

01.1-表征性

在理解 REST 中的表征性(REpresentational )之前,首先需要明确的是资源(Resource)这一概念。假设你正在观看一部影视作品,那么它就可以称为是一种资源。如果你在开发一个公司内部的人力资源系统,每个雇员也可看作是人力资源系统中的一种资源。


表征(REpresentation)指的是信息与用户交互时的表示形式[2]。例如,我们可以向系统请求 Json 格式的电影信息,也可以请求 HTML 形式的信息,甚至是直接请求媒体文件。

01.2-状态

REST 中的状态(State)是指在特定语境中才能产生的上下文信息。例如,当观看连续剧时,当前剧集的上一集、下一集信息。这些都是一些相对的信息,只有在特定的语境下才有意义。


所以,当我们向某个 RESTful 服务请求某个资源时,我们除了获得该资源的特定表征信息外,我们还应当能够从响应中获得一些上下文信息。

01.3-转移

REST 中的转移(Transfer)是指服务器通过某种方式完成在不同状态之间的转变。例如,通过响应中的下一集信息向服务器获取下一集剧集资源。

02-Spring Boot 中构建 RESTful 服务

依赖:org.springframework.boot:spring-boot-starter-weborg.springframework.boot:spring-boot-starter-data-jpacom.h2database:h2


首先,需要一个资源类实体Employee:


@Entityclass Employee {    private @Id @GeneratedValue Long id;    private String name;    private String role;    // 省略了 getters / setters }
复制代码


其次,围绕该资源定义一部分 API(完整版的代码可以从 gitee 下载):


@RestControllerclass EmployeeController {    @GetMapping("/employees")    List<Employee> all() { ... }    @PostMapping("/employees")    Employee newEmployee(@RequestBody Employee newEmployee) { ... }    @GetMapping("/employees/{id}")    Employee one(@PathVariable Long id) { ... }    @PutMapping("/employees/{id}")    Employee replaceEmployee(@RequestBody Employee newEmployee, @PathVariable Long id) { ... }    @DeleteMapping("/employees/{id}")    void deleteEmployee(@PathVariable Long id) { ... }}
复制代码


然后,通过 Spring-Data-JPA,定义并生成一个持久化层(非常简单):


public interface EmployeeRepository extends JpaRepository<Employee, Long> {}
复制代码


最后,将所有的部分拼装在一起:


@SpringBootApplicationpublic class PayrollApplication {  private final static Logger LOGGER = LoggerFactory.getLogger(PayrollApplication.class);  public static void main(String[] args) {    SpringApplication.run(PayrollApplication.class, args);  }  @Bean  CommandLineRunner initDatabase(EmployeeRepository repository) {      return args -> {          LOGGER.info("Preloading {}", repository.save(new Employee("Bilbo Baggins", "burglar")));          LOGGER.info("Preloading {}", repository.save(new Employee("Frodo Baggins", "thief")));      };  }}
复制代码


我们来运行一下,mvn spring-boot:run。访问浏览器http://localhost:8080/employees


但这样一个接口能称为是 RESTful 接口吗?根据前一章节的分析,显然是不能的。

02.1-如何使接口更具 RESTful 风格

依赖:org.springframework.boot:spring-boot-starter-hateoas


我们借助 hateoas 为接口的返回值添加上下文信息,来使接口返回值具有状态。在前面定义 API 接口时,我们定义了一个all来返回所有的雇员信息,接下来我们又定义了一个可以返回某个特定雇员信息(by id)的接口one。很容易能够看出,某个特定雇员的信息是所有雇员信息的一部分。


我们对one方法进行如下改造:


@GetMapping("/employees/{id}")EntityModel<Employee> one(@PathVariable Long id) {
Employee employee = repository.findById(id) .orElseThrow(() -> new EmployeeNotFoundException(id));
return EntityModel.of(employee, linkTo(methodOn(EmployeeController.class).one(id)).withSelfRel(), linkTo(methodOn(EmployeeController.class).all()).withRel("employees"));}
复制代码


我们首先将one的返回值从Employee修改为了EntityModel<Employee>EntityModel<T>是 spring-hateoas 中定义的 entity 的包装类,它包含了原始的 entity,以及它的一些相关链接。


然后,我们通过EntityModel.of方法将employee与两个链接关联在一起:


  1. 指向其自身的链接

  2. 指向其所属的全部雇员信息的链接


可以看出来,这些链接表述的其实是雇员信息(雇员资源的一种表征)的上下文关系,即状态。当我们再次运行程序,并访问http://localhost:8080/employees/1时,返回值就变成了如下的模样:


{  "id": 1,  "name": "Bilbo Baggins",  "role": "burglar",  "_links": {    "self": {      "href": "http://localhost:8080/employees/1"    },    "employees": {      "href": "http://localhost:8080/employees"    }  }}
复制代码


其中:_links就是 spring-hateoas 为其增加的上下文信息,搭救对应了上面提到的两个链接。


同样地,我们对all方法进行改造:


@GetMapping("/employees")CollectionModel<EntityModel<Employee>> all() {    final List<EntityModel<Employee>> employees = repository.findAll().stream()            .map(employee -> EntityModel.of(employee,                    linkTo(methodOn(EmployeeController.class).one(employee.getId())).withSelfRel(),                    linkTo(methodOn(EmployeeController.class).all()).withRel("employees")))            .collect(Collectors.toList());
return CollectionModel.of(employees, linkTo(methodOn(EmployeeController.class).all()).withSelfRel());
}
复制代码

refs

[1] Building REST services with Spring

[2] 09 | RESTful服务(上):从面向过程编程到面向资源编程

[3] 10 | RESTful服务(下):如何评价服务是否RESTful?

[4] What is REST

发布于: 刚刚阅读数: 6
用户头像

Samson

关注

还未添加个人签名 2019.07.22 加入

InfoQ签约作者 | 阿里云社区签约作者

评论

发布
暂无评论
Spring Boot「01」构建 REST API_Spring Boot_Samson_InfoQ写作社区