前言
关于 REST 概念的阐述,是 Roy Fielding 大神在他的 博士论文 中提出的,有兴趣的小伙伴可以去看一下,Restful web service是指遵守了 REST 风格的web服务, 可以看下阮一峰老师的 RESTful API 最佳实践。但要记住的是 REST 是一种设计风格,它背后的理念是使用 HTTP 动词 GET,POST, PUT, DELETE 来对应服务的 CURD 操作,并且使用 JSON 来请求数据和接收数据。
在设计符合 REST 理念的服务接口时,可以参考以下指导方针:
- 使用 HTTP 动词(GET, POST, PUT, DELETE)围绕服务展开操作
- 使用 URI 来传达意图
- 请求和响应使用 JSON
- 使用 HTTP 状态码来传达结果
下面使用一个对员工进行增删改查的例子来实践 RESTful 设计。
创建项目
项目结构图如下:
pom 依赖文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>top.yekongle</groupId>
<artifactId>springboot-restful-sample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-restful-sample</name>
<description>RESTful project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--In-Memory Database-->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
代码编写
Employee.java, 员工实体类
package com.yekongle.rest.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.Data;
/**
* @Description: 持久化实体类
* @Data: lombok 注解,自动生成 getter, setter方法,重写equals,hash,toString方法
* @Entity: 表明该类是持久化实体类(映射到对应table)
* @Id: 指定 table id
* @GeneratedValue: 默认采用自增长策略
* @Author: Yekongle
* @Date: Apr 7, 2020
*/
@Data
@Entity
public class Employee {
@Id
@GeneratedValue
private Long id;
private String name;
private String role;
public Employee() {
}
public Employee(String name, String role) {
this.name = name;
this.role = role;
}
}
EmployeeNotFoundException.java, 自定义 Exception,找不到员工时抛出该 Exception
package top.yekongle.restful.exception;
/**
* @Description: 自定义employee exception
* @Author: Yekongle
* @Date: Apr 7, 2020
*/
public class EmployeeNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public EmployeeNotFoundException(Long id) {
super("Could not find employee " + id);
}
}
EmployeeRepository.java, 员工数据操作接口
package top.yekongle.restful.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import top.yekongle.restful.entity.Employee;
/**
* @Description: 数据操作接口,继承JpaRepository
* @Author: Yekongle
* @Date: Apr 7, 2020
*/
public interface EmployeeRepository extends JpaRepository<Employee, Long>{
}
GlobalExceptonConfig.java, 全局 Exception 配置
package top.yekongle.restful.config;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import top.yekongle.restful.exception.EmployeeNotFoundException;
/**
* @Description: 全局异常配置
* @Author: Yekongle
* @Date: Apr 7, 2020
*/
@ControllerAdvice
public class GlobalExceptonConfig {
/**
* @ResponseBody 方法返回结果直接写入到 http reponse中
* @ExceptionHandler 捕捉指定的exception
* @ResponseStatus 指定response的http status:2xx/4xx/5xx
* */
@ResponseBody
@ExceptionHandler(EmployeeNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String employeeNotFoundHandler(EmployeeNotFoundException ex) {
return ex.getMessage();
}
@ResponseBody
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String systemErrorHandler(Exception ex) {
return ex.getMessage();
}
}
DatabaseConfig.java, 数据库配置,插入两条初始化数据
package top.yekongle.restful.config;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import lombok.extern.slf4j.Slf4j;
import top.yekongle.restful.entity.Employee;
import top.yekongle.restful.repository.EmployeeRepository;
/**
* @Description: 数据库配置
* @Configuration: 表明这个是配置类
* @Sl4j: lombok注解,自动生成Logger
* @Author: Yekongle
* @Date: Apr 7, 2020
*/
@Configuration
@Slf4j
public class DatabaseConfig {
/**
* 应用上文下加载完后SpringBoot会执行所有注册到Spring中的CommandLineRunner
* */
@Bean
public CommandLineRunner initDatabase(EmployeeRepository repository) {
// 执行CommandLineRunner的回调函数, 往数据库插入初始数据
return args -> {
log.info("Preloading " + repository.save(new Employee("张三", "初级程序员")));
log.info("Preloading " + repository.save(new Employee("李四", "高级程序员")));
};
}
}
EmployeeController.java,表现层,处理请求 API
package top.yekongle.restful.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import top.yekongle.restful.entity.Employee;
import top.yekongle.restful.exception.EmployeeNotFoundException;
import top.yekongle.restful.repository.EmployeeRepository;
/**
* @Description: 员工controller类,
* @Author: Yekongle
* @Date: Apr 7, 2020
*/
// @RestController 注解会使每个方法返回值直接写入到response body中,而不是去渲染模板
@RestController
public class EmployeeController {
@Autowired
EmployeeRepository repository;
/**
* 查找所有员工
*/
@GetMapping("/employees")
public ResponseEntity<?> all() {
List<Employee> employeeList = repository.findAll();
return ResponseEntity.ok(employeeList);
}
/**
* 新建员工
*/
@PostMapping("/employees")
public ResponseEntity<?> newEmployee(@RequestBody Employee newEmployee) {
repository.save(newEmployee);
return ResponseEntity.status(HttpStatus.CREATED).body(newEmployee);
}
/**
* 根据id查找员工
* @PathVariable 将参数与请求路径模板绑定起来,{id}的值就是参数id的值
*/
@GetMapping("/employees/{id}")
public ResponseEntity<?> one(@PathVariable Long id) {
// 根据id查找员工,找不到抛出一个自定义 exception
Employee employee = repository.findById(id).orElseThrow(() -> new EmployeeNotFoundException(id));
return ResponseEntity.ok(employee);
}
/**
* 更新员工信息
* @PathVariable 将参数与请求路径模板绑定起来,{id}的值就是参数id的值
*/
@PutMapping("/employees/{id}")
public ResponseEntity<?> replaceEmployee(@RequestBody Employee newEmployee, @PathVariable Long id) {
// 根据id查找员工,找到就update信息再保存,找不到就直接新建一个员工
repository.findById(id)
.map(employee -> {
employee.setName(newEmployee.getName());
employee.setRole(newEmployee.getRole());
return repository.save(employee);
})
.orElseGet(() -> {
newEmployee.setId(id);
return repository.save(newEmployee);
});
return ResponseEntity.noContent().build();
}
/**
* 删除员工
* @PathVariable 将参数与请求路径模板绑定起来,{id}的值就是参数id的值
*/
@DeleteMapping("/employees/{id}")
public ResponseEntity<?> deleteEmployee(@PathVariable Long id) {
repository.deleteById(id);
return ResponseEntity.noContent().build();
}
}
运行测试
启动项目,查看控制台日志可看到两条初始化数据插入Database
使用 Postman 对接口进行测试
查询所有员工:http://localhost:8080/employees
查询某个员工:http://localhost:8080/employees/1
查询某个不存在的员工:http://localhost:8080/employees/3
将 id 为 1 的员工名字改为赵六:http://localhost:8080/employees/1
新增员工:http://localhost:8080/employees
再查看所有员工,可见新增了王五的记录
删除员工:http://localhost:8080/employees/3
项目已上传至 Github: https://github.com/yekongle/springboot-code-samples/tree/master/springboot-restful-sample , 希望对小伙伴们有帮助哦。