Spring Boot2 实战系列之RESTful Web Service

前言

关于 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 , 希望对小伙伴们有帮助哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章