![如何使用 Spring Boot 构建一个 RESTful Web 服务插图 如何使用 Spring Boot 构建一个 RESTful Web 服务插图](https://blog.eswlnk.com/wp-content/uploads/wpcy/4b6b8362b629ea83ff75f3a211746de3.jpg)
本文将介绍如何使用 Spring Boot 构建 RESTful Web 服务,主要关注项目的结构、注解的使用和单元测试代码的编写,并由此探索 Spring Boot 的设计理念与使用方法。
项目结构
本文使用三层架构代码结构,其中 src/main/java
目录下主要分三个目录:controller
、model
和 service
。处理请求和返回响应的逻辑控制器代码需要放在 controller
目录下;表示数据对象的 POJO 类需要方在 model
目录下;主要的业务逻辑代码需要抽取到服务中,然后放在 service
目录下(service
目录下是接口类,impl
目录下是实现类)。
![如何使用 Spring Boot 构建一个 RESTful Web 服务插图1 如何使用 Spring Boot 构建一个 RESTful Web 服务插图1](https://static.eswlnk.com/2023/06/20230606124245164.png)
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
用于存放测试代码,测试代码应与被测试代码使用相同的包名。
源码分析
下面分析该项目的源码,以期对 Spring Boot 的使用有一个基本的了解。
pom.xml 代码
Spring Boot 提供各类封装好的 Starter(以 spring-boot-starter-*
格式命名)供我们去使用,当需要某项依赖时,直接在 pom.xml
引用对应的 Starter 即可。
本文使用 Maven 管理依赖,pom.xml
源码如下:
<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
,提供了对程序打包和运行的支持。
![如何使用 Spring Boot 构建一个 RESTful Web 服务插图2 如何使用 Spring Boot 构建一个 RESTful Web 服务插图2](https://static.eswlnk.com/2023/06/20230606124302819.png)
启动类代码
程序入口类 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
:扫描指定路径,将其中带有@Controller
、@Service
、@Repository
和@Component
注解的类注册到 Spring 容器中。
因此,@SpringBootApplication
可以简化一些常见配置的使用,使得程序的启动更为便捷。
控制器代码
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.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> getUserList() {
return userService.getUserList();
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
@PostMapping("")
public User createUser(@Validated @RequestBody User user) {
return userService.createUser(user);
}
@PutMapping("/{id}")
public User updateUserById(@PathVariable Long id, @Validated @RequestBody User user) {
return userService.updateUserById(id, user);
}
@DeleteMapping("/{id}")
public void deleteUserById(@PathVariable Long id) {
userService.deleteUserById(id);
}
}
该类使用了 @RestController
注解,并在类上使用了 @RequestMapping("/users")
,表示映射到 /users
路径下。
其中,@Autowired
注解实现了自动装配,将 UserService
接口的实现类 UserServiceImpl
注入到了 UserController
类中。
该类包含五个方法,分别对应了 RESTful API 的 GET、POST、PUT 和 DELETE 请求。
getUserList()
方法映射了 GET 请求,返回所有用户的列表;getUserById(@PathVariable Long id)
方法映射了 GET 请求,通过 id 返回指定用户;createUser(@Validated @RequestBody User user)
方法映射了 POST 请求,创建一个新的用户并返回创建后的用户对象;updateUserById(@PathVariable Long id, @Validated @RequestBody User user)
方法映射了 PUT 请求,通过 id 更新用户信息,并返回更新后的用户对象;deleteUserById(@PathVariable Long id)
方法映射了 DELETE 请求,通过 id 删除指定用户。
这些方法的注解说明如下:
@GetMapping("/{id}")
:表示映射 GET 请求,其中{id}
部分为路径变量;@PostMapping("")
:表示映射 POST 请求,其中空字符串表示该请求路径为/users
;@PutMapping("/{id}")
:表示映射 PUT 请求,其中{id}
部分为路径变量;@DeleteMapping("/{id}")
:表示映射 DELETE 请求,其中{id}
部分为路径变量;@Validated
:表示启用方法参数验证;@RequestBody
:表示将 HTTP 请求正文转换为 Java 对象。
服务代码
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.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class UserServiceImpl implements UserService {
// 模拟数据库
private static Map<Long, User> userMap = new HashMap<>();
@Override
public List<User> getUserList() {
return new ArrayList<>(userMap.values());
}
@Override
public User getUserById(Long id) {
return userMap.get(id);
}
@Override
public User createUser(User user) {
Long id = (long) (userMap.size() + 1);
user.setId(id);
userMap.put(id, user);
return user;
}
@Override
public User updateUserById(Long id, User user) {
User oldUser = userMap.get(id);
oldUser.setName(user.getName());
oldUser.setAge(user.getAge());
oldUser.setAddress(user.getAddress());
userMap.put(id, oldUser);
return oldUser;
}
@Override
public void deleteUserById(Long id) {
userMap.remove(id);
}
}
该类实现了 UserService
接口,并使用了 @Service
注解,表示将其注册到 Spring 容器中。
其中,定义了一个静态变量 userMap
,用于存储模拟的用户数据。该类实现了 getUserList()
、getUserById()
、createUser()
、updateUserById()
和 deleteUserById()
方法,这些方法的具体实现可以根据业务需求进行修改。
单元测试代码
src/test/java/com/example/demo/controller/UserControllerTest.java
的代码如下:
package com.example.demo.controller;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
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.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import java.util.ArrayList;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@SpringJUnitConfig
@AutoConfigureMockMvc
@SpringBootTest
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Mock
private UserService userService;
@InjectMocks
private UserController userController;
private List<User> users = new ArrayList<>();
private User user1, user2;
@BeforeEach
public void setup() {
user1 = new User(1L, "张三", 20, "北京市海淀区");
user2 = new User(2L, "李四", 22, "北京市朝阳区");
users.add(user1);
users.add(user2);
}
@Test
public void getUserList() throws Exception {
when(userService.getUserList()).thenReturn(users);
mockMvc.perform(MockMvcRequestBuilders.get("/users"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.size()").value(2))
.andExpect(MockMvcResultMatchers.jsonPath("$.[0].name").value("张三"))
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON));
}
@Test
public void getUserById() throws Exception {
when(userService.getUserById(1L)).thenReturn(user1);
mockMvc.perform(MockMvcRequestBuilders.get("/users/1"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("张三"))
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON));
}
@Test
public void createUser() throws Exception {
User user = new User(null, "王五", 25, "北京市丰台区");
User savedUser = new User(3L, "王五", 25, "北京市丰台区");
when(userService.createUser(any(User.class))).thenReturn(savedUser);
mockMvc.perform(MockMvcRequestBuilders.post("/users")
.content(new ObjectMapper().writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.id").value(3))
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("王五"))
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON));
}
@Test
public void updateUserById() throws Exception {
User newUser = new User(null, "王五", 25, "北京市丰台区");
User updatedUser = new User(1L, "王五", 25, "北京市朝阳区");
when(userService.updateUserById(1L, newUser)).thenReturn(updatedUser);
mockMvc.perform(MockMvcRequestBuilders.put("/users/1")
.content(new ObjectMapper().writeValueAsString(newUser))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1))
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("王五"))
.andExpect(MockMvcResultMatchers.jsonPath("$.address").value("北京市朝阳区"))
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON));
}
@Test
public void deleteUserById() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.delete("/users/2"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
该测试类使用了 Spring Boot 提供的测试框架,其中使用了 @Mock
注解和 @InjectMocks
注解,分别用于模拟 UserService
和 UserController
。
测试类包含了四个测试方法,分别测试了四种 HTTP 请求。其中,通过 MockMvc
对象模拟了 HTTP 请求,并使用 andExpect()
方法对返回结果进行判断,判断响应头、响应状态码、响应内容等是否符合预期。
总结
本文介绍了如何使用 Spring Boot 构建 RESTful Web 服务。通过实现控制器、服务和单元测试代码,展示了 Spring Boot 的注解使用、自动装配和启动方式。
在实际开发中,这种结构清晰的代码架构可以使项目更加易于维护和扩展,同时也提高了代码的可读性和可测试性,为后续的项目迭代提供了一定的便利。
📮评论