跳到主要内容Spring Boot RESTful API 开发与测试实战 | 极客日志Javajava
Spring Boot RESTful API 开发与测试实战
综述由AI生成Spring Boot RESTful API 开发涉及资源设计、分层架构实现及自动化测试。了 Controller-Service-Repository 标准结构,展示了如何使用 JPA 和 H2 快速构建数据接口。重点对比了单元测试、集成测试与 Mock 测试的适用场景,并深入探讨了 Spring Security 基础认证与 JWT 无状态鉴权的实现差异。通过修复代码格式、优化注释及整合测试用例,提供了一套可直接参考的生产级开发模板,帮助开发者高效构建安全可靠的 Web 服务。
活在当下3 浏览 Spring Boot RESTful API 开发与测试

核心概念与 HTTP 规范
RESTful API 是目前 Java 后端开发的主流设计风格。它基于 REST 架构风格,核心在于资源(Resource)的抽象、表现层(Representation)的标准化以及状态转移(State Transfer)。
在设计时,我们主要关注两点:
- URI 设计:用路径表示资源,例如
/api/products。
- HTTP 方法:用动词表示操作,GET 获取,POST 创建,PUT 更新,DELETE 删除。PATCH 用于部分更新。
响应状态码同样关键,200 代表成功,201 是资源创建成功,400 到 500 系列则对应客户端错误或服务器异常。理解这些基础,才能写出规范的接口。
项目搭建与分层开发
开发一个标准的 Spring Boot RESTful 服务,通常遵循 Controller-Service-Repository 的分层架构。这样能保持代码清晰,便于维护。
依赖配置
首先,在 pom.xml 中引入必要的依赖。Web 模块负责处理请求,Data JPA 简化数据库操作,H2 作为内存数据库方便演示,测试依赖必不可少。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
com.h2database
h2
runtime
org.springframework.boot
spring-boot-starter-test
test
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<scope>
</scope>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<scope>
</scope>
</dependency>
</dependencies>
实体类定义
实体类映射数据库表。注意使用 JPA 注解来描述字段关系和主键策略。
import javax.persistence.*;
@Entity
@Table(name = "product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productId;
private String productName;
private double price;
private int sales;
public Product() {}
public Product(String productId, String productName, double price, int sales) {
this.productId = productId;
this.productName = productName;
this.price = price;
this.sales = sales;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getProductId() { return productId; }
public void setProductId(String productId) { this.productId = productId; }
public String getProductName() { return productName; }
public void setProductName(String productName) { this.productName = productName; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
public int getSales() { return sales; }
public void setSales(int sales) { this.sales = sales; }
@Override
public String toString() {
return "Product{" +
"id=" + id +
", productId='" + productId + '\'' +
", productName='" + productName + '\'' +
", price=" + price +
", sales=" + sales +
'}';
}
}
数据访问层 (Repository)
继承 JpaRepository 可以自动获得基础的 CRUD 能力。如果需要自定义查询,可以直接在接口方法名中约定规则。
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findBySalesGreaterThan(int sales);
}
业务逻辑层 (Service)
Service 层负责事务管理和复杂逻辑。注意加上 @Transactional 注解,确保数据一致性。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Transactional
public void addProduct(Product product) {
productRepository.save(product);
}
@Transactional
public void updateProduct(Product product) {
productRepository.save(product);
}
@Transactional
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
@Transactional(readOnly = true)
public List<Product> getAllProducts() {
return productRepository.findAll();
}
@Transactional(readOnly = true)
public List<Product> getTopSellingProducts(int topN) {
List<Product> products = productRepository.findBySalesGreaterThan(0);
products.sort((p1, p2) -> p2.getSales() - p1.getSales());
if (products.size() > topN) {
return products.subList(0, topN);
}
return products;
}
}
控制层 (Controller)
Controller 暴露 HTTP 接口。使用 @RestController 组合了 @Controller 和 @ResponseBody。返回 ResponseEntity 可以更灵活地控制状态码。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/products")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/")
public ResponseEntity<List<Product>> getAllProducts() {
List<Product> products = productService.getAllProducts();
return new ResponseEntity<>(products, HttpStatus.OK);
}
@PostMapping("/")
public ResponseEntity<Void> addProduct(@RequestBody Product product) {
productService.addProduct(product);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@PutMapping("/{id}")
public ResponseEntity<Void> updateProduct(@PathVariable Long id, @RequestBody Product product) {
product.setId(id);
productService.updateProduct(product);
return new ResponseEntity<>(HttpStatus.OK);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@GetMapping("/top-selling")
public ResponseEntity<List<Product>> getTopSellingProducts(@RequestParam int topN) {
List<Product> products = productService.getTopSellingProducts(topN);
return new ResponseEntity<>(products, HttpStatus.OK);
}
}
自动化测试策略
测试是保证代码质量的关键。Spring Boot 提供了丰富的测试支持,主要分为单元测试、集成测试和 Mock 测试。
单元测试
单元测试关注单个 Service 方法的功能。不需要启动整个 Web 容器,速度最快。
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
class ProductServiceTests {
@Autowired
private ProductService productService;
@Test
void testAddProduct() {
Product product = new Product("P006", "平板", 2000.0, 70);
productService.addProduct(product);
List<Product> products = productService.getAllProducts();
assertThat(products).hasSize(6);
}
}
集成测试
集成测试验证 Controller 与数据库、网络层的交互。使用 RANDOM_PORT 模拟真实环境。
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ProductControllerTests {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
void testGetAllProducts() {
List<Product> products = restTemplate.getForObject(
"http://localhost:" + port + "/api/products/", List.class);
assertThat(products).hasSize(5);
}
}
Mock 测试
Mock 测试专注于 Controller 层,隔离 Service 依赖。使用 @WebMvcTest 和 @MockBean 可以快速验证请求路由和参数绑定,无需连接数据库。
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Arrays;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(ProductController.class)
class ProductControllerMockTests {
@Autowired
private MockMvc mockMvc;
@MockBean
private ProductService productService;
@Test
void testGetAllProducts() throws Exception {
List<Product> products = Arrays.asList(
new Product("P001", "手机", 1000.0, 100),
new Product("P002", "电脑", 5000.0, 50)
);
when(productService.getAllProducts()).thenReturn(products);
mockMvc.perform(get("/api/products/"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON));
verify(productService, times(1)).getAllProducts();
}
}
安全认证与授权
生产环境的 API 必须考虑安全性。Spring Security 提供了强大的框架,支持 Basic Auth 和 JWT 等多种模式。
Spring Security 基础
通过配置类设置用户认证和权限控制。这里演示了简单的内存用户配置。
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("admin").password("admin123").roles("ADMIN")
.and()
.withUser("user").password("user123").roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/products/top-selling").hasRole("ADMIN")
.antMatchers("/api/products/**").hasRole("USER")
.and().httpBasic();
}
}
JWT 无状态认证
对于前后端分离的项目,JWT (JSON Web Token) 更为常用。客户端登录后获取 Token,后续请求携带该 Token 即可。
需要引入 jjwt 依赖,并编写工具类生成和解析 Token。
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username);
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public Boolean validateToken(String token, String username) {
final String extractedUsername = extractUsername(token);
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
}
配合过滤器 JwtRequestFilter 拦截请求,解析 Token 并设置 Spring Security 上下文,即可实现无状态认证。
实际应用场景
RESTful API 的应用非常广泛,从商品管理到订单系统,再到博客平台,核心逻辑都是资源的增删改查。
在实际项目中,建议根据业务复杂度选择合适的测试策略和安全方案。例如,内部管理系统可用 Basic Auth,而面向公众的 App 则推荐 JWT。
运行应用后,访问 http://localhost:8080/api/products/ 即可查看产品列表,带上 Authorization 头可测试权限控制效果。
总结
本章涵盖了 Spring Boot RESTful API 的全流程开发。从 REST 规范的理解,到分层架构的代码实现,再到多层次的测试策略和安全机制,这些都是构建高质量后端服务的基石。掌握这些内容,你就能从容应对大多数 Java Web 开发需求。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online