前言
關於 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 , 希望對小夥伴們有幫助哦。