如何使用 Spring Boot 构建一个 RESTful Web 服务?
本文实现的 RESTful Web 服务提供用户(User)的增删改查功能,内部使用 Java ArrayList 实现数据的存储。
写作本文时,所使用的 JDK 版本、Maven 版本和 Spring Boot 版本分别为:
- JDK 版本:BellSoft Liberica JDK 17
- Maven 版本:3.9.2
- Spring Boot 版本:3.1.0
1 项目结构
本文使用三层架构代码结构,src/main/java
下主要分三个目录:controller
、model
和service
。处理请求和返回响应的逻辑控制器代码需要放在controller
目录下;表示数据对象的 POJO 类需要方在model
目录下;主要的业务逻辑代码需要抽取到服务中,然后放在service
目录下(service
目录下是接口类,impl
目录下是实现类)。
spring-boot-restful-service-demo
├─ src/main/java
│ └─ com.example.demo
│ ├─ controller
│ │ └─ UserController.java
│ ├─ model
│ │ └─ User.java
│ ├─ service
│ │ ├─ UserService.java
│ │ └─ impl
│ │ └─ UserServiceImpl.java
│ └─ DemoApplication.java
├─ src/test/java
│ └─ com.example.demo
│ └─ controller
│ └─ UserControllerTest.java
└─ pom.xml
此外,src/test/java
用于存放测试代码,测试代码应与被测试代码使用相同的包名。
2 源码分析
下面分析下该项目的源码,以期对 Spring Boot 的使用有一个基本的了解。
2.1 pom.xml 代码
Spring Boot 提供各类封装好的 Starter(以spring-boot-starter-*
格式命名)供我们去使用,当需要某项依赖时,直接在pom.xml
引用对应的 Starter 即可。
本文使用 Maven 管理依赖,pom.xml
源码如下:
<?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>3.1.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
可以看到,本文的示例项目使用了三个 Starter:spring-boot-starter-web
、spring-boot-starter-validation
和spring-boot-starter-test
。
-
spring-boot-starter-web
包含了编写 Spring Web 程序相关的所有依赖,如编写 RESTful 接口相关的依赖、Spring MVC 相关的依赖、程序的运行时服务器(默认为 Apache Tomcat)相关的依赖等; -
spring-boot-starter-validation
包含了请求参数校验相关的所有依赖; -
spring-boot-starter-test
包含了测试 Spring Boot 程序的所有依赖,如 JUnit Jupiter、Hamcrest 和 Mockito 等。
此外,还使用了一个插件spring-boot-maven-plugin
,提供了对程序打包和运行的支持。
2.2 启动类代码
程序入口类src/main/java/com/example/demo/DemoApplication.java
的代码如下:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
从启动类即可以看到,Spring Boot 应用程序无须web.xml
等冗长的配置文件,使用纯 Java 注解的方式即可进行配置。
可以看到,该类只使用了一个注解:@SpringBootApplication
,该注解是一个便捷注解,其包含了如下三个注解:
@Configuration
:用于定义配置类;@EnableAutoConfiguration
:用于自动装入应用程序所需的所有 Bean;@ComponentScan
:扫描指定路径,将类装配到 Spring 容器中。
2.3 Controller 代码
控制器类src/main/java/com/example/demo/controller/UserController.java
的代码如下:
package com.example.demo.controller;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/")
public List<User> getUsers() {
return userService.getUsers();
}
@GetMapping("/{id}")
public User getUser(@PathVariable("id") Integer id) {
return userService.getUser(id);
}
@PostMapping("/")
@ResponseStatus(HttpStatus.CREATED)
public void addUser(@RequestBody @Validated User user) {
userService.addUser(user);
}
@PatchMapping("/")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void updateUser(@RequestBody @Validated User user) {
userService.updateUser(user);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable("id") Integer id) {
userService.deleteUser(id);
}
}
该类中:
- 注解
@RestController
相当于是@Controller
和@ResponseBody
两个注解的结合,表示该类提供 Web 接口,该类中的方法会处理 HTTP 请求并以 JSON 响应客户端; - 注解
@GetMapping
表示方法接收 HTTP GET 请求,而@PostMapping
、@PatchMapping
和@DeleteMapping
分别表示方法接收 POST、PATCH 和 DELETE 请求; - 注解
@ResponseStatus
用于指定方法返回的 HTTP 状态码; - 注解
@RequestBody
用于指定接收 JSON 请求体的对象,@PathVariable
用于获取 URL 路径中对应的参数值。
2.4 Service 代码
服务接口类src/main/java/com/example/demo/service/UserService.java
的代码如下:
package com.example.demo.service;
import com.example.demo.model.User;
import java.util.List;
public interface UserService {
List<User> getUsers();
User getUser(Integer id);
void addUser(User user);
void updateUser(User user);
void deleteUser(Integer id);
}
服务实现类src/main/java/com/example/demo/service/impl/UserServiceImpl.java
的代码如下:
package com.example.demo.service.impl;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
private static final List<User> USERS = new ArrayList<>();
@Override
public List<User> getUsers() {
return USERS;
}
@Override
public User getUser(Integer id) {
int i = findUserIndex(id);
if (i < USERS.size()) {
return USERS.get(i);
}
return null;
}
@Override
public void addUser(User user) {
USERS.add(user);
}
@Override
public void updateUser(User user) {
int i = findUserIndex(user.id());
// update
if (i < USERS.size()) {
USERS.set(i, user);
}
}
@Override
public void deleteUser(Integer id) {
int i = findUserIndex(id);
// update
if (i < USERS.size()) {
USERS.remove(i);
}
}
private int findUserIndex(Integer userId) {
int i = 0;
for (; i < USERS.size(); i++) {
if (USERS.get(i).id().equals(userId)) {
break;
}
}
return i;
}
}
可以看到,Service 使用ArrayList
来存储数据,并提供对 User 增、删、改、查的支持。
3 程序运行与测试
打开命令行,在程序根目录执行如下 Maven 命令启动应用程序:
mvn spring-boot:run
程序启动后,命令行执行如下 CURL 命令新建三个 User:
curl -X POST -H "Content-Type: application/json" -d '{"id": 1, "name": "Larry", "age": 28}' http://localhost:8080/users/
curl -X POST -H "Content-Type: application/json" -d '{"id": 2, "name": "Lucy", "age": 18}' http://localhost:8080/users/
curl -X POST -H "Content-Type: application/json" -d '{"id": 3, "name": "Jacky", "age": 30}' http://localhost:8080/users/
执行如下 CURL 命令 查询全部 User:
curl -X GET http://localhost:8080/users/
[{"id":1,"name":"Larry","age":28},{"id":2,"name":"Lucy","age":18},{"id":3,"name":"Jacky","age":30}]
执行如下 CURL 命令更新 ID 为 3 的 User:
curl -X PATCH -H "Content-Type: application/json" -d '{"id": 3, "name": "Alan", "age": 29}' http://localhost:8080/users/
执行如下 CURL 命令查询 ID 为 3 的 User,发现信息已被更新:
curl -X GET http://localhost:8080/users/3
{"id":3,"name":"Alan","age":29}
执行如下 CURL 命令删除 ID 为 3 的 User:
curl -X DELETE http://localhost:8080/users/3
再次查询所有 User,发现 ID 为 3 的 User 已被删除:
curl -X GET http://localhost:8080/users/
[{"id":1,"name":"Larry","age":28},{"id":2,"name":"Lucy","age":18}]
4 添加单元测试代码
控制器测试类src/test/java/com/example/demo/controller/UserControllerTest.java
的代码如下:
package com.example.demo.controller;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mvc;
@BeforeEach
public void createUser() throws Exception {
String body = "{\"id\": 1, \"name\": \"Larry\", \"age\": 28}";
mvc.perform(MockMvcRequestBuilders.post("/users/").contentType(MediaType.APPLICATION_JSON).content(body)).andExpect(status().isCreated());
}
@Test
public void testGetUsers() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/users/")).andExpect(status().isOk()).andExpect(MockMvcResultMatchers.jsonPath("$").isArray());
}
@Test
public void testGetUser() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/users/{id}", 1)).andExpect(status().isOk()).andExpect(MockMvcResultMatchers.jsonPath("$.name").value("Larry"));
}
@Test
public void testAddUser() throws Exception {
String body = "{\"id\": 2, \"name\": \"Lucy\", \"age\": 18}";
mvc.perform(MockMvcRequestBuilders.post("/users/").contentType(MediaType.APPLICATION_JSON).content(body)).andExpect(status().isCreated());
}
@Test
public void testUpdateUser() throws Exception {
String body = "{\"id\": 1, \"name\": \"Larry\", \"age\": 29}";
mvc.perform(MockMvcRequestBuilders.patch("/users/").contentType(MediaType.APPLICATION_JSON).content(body)).andExpect(status().isNoContent());
}
@Test
public void testDeleteUser() throws Exception {
mvc.perform(MockMvcRequestBuilders.delete("/users/{id}", 1)).andExpect(status().isNoContent());
}
}
可以看到,如上代码使用MockMvc
实现了对UserController
的单元测试。
综上,本文完成了对 Spring Boot RESTful 服务的搭建,了解了 Spring Boot 常用注解的含义和使用方式。本文涉及的完整项目代码已托管至「GitHub」,欢迎关注或 Fork。
参考资料
[1] Building a RESTful Web Service | Spring - spring.io
[2] Building an Application with Spring Boot | Spring - spring.io
转载自:https://juejin.cn/post/7352079330372141110