跳到主要内容
Spring Boot RESTful API 开发与测试 | 极客日志
Java java
Spring Boot RESTful API 开发与测试 本文详细讲解了 Spring Boot RESTful API 的开发全流程,涵盖 RESTful 设计规范、分层架构实现、多维度测试策略(单元、集成、Mock)以及安全认证方案(Spring Security 与 JWT)。通过商品管理模块的实战代码示例,展示了如何构建高可用、易维护的 Web 接口,适合希望提升后端工程能力的开发者参考。
狂少 发布于 2026/3/30 更新于 2026/4/25 1 浏览Spring Boot RESTful API 开发与测试
学习目标与重点提示
本章我们将深入掌握 Spring Boot RESTful API 的核心开发流程,包括设计原则、代码实现、分层测试以及安全认证。重点在于理解 RESTful 的资源导向思想,熟练运用 Spring MVC 注解构建接口,并掌握单元测试、集成测试及 Mock 测试的区别与应用场景。
RESTful API 概述
RESTful API 是目前 Java Web 开发中最主流的设计风格。它基于 REST(Representational State Transfer)架构风格,核心在于通过 URI 表示资源,利用 HTTP 方法(GET、POST、PUT、DELETE 等)操作资源,并通过状态码反馈结果。
核心特点
资源(Resource) :一切皆资源,使用 URI 唯一标识。
表现层(Representation) :客户端与服务器交互的数据格式,通常是 JSON。
状态转移(State Transfer) :HTTP 请求方法驱动资源状态的改变。
常用 HTTP 方法与状态码
方法 说明 GET 获取资源 POST 创建资源 PUT 更新资源 DELETE 删除资源 PATCH 部分更新
常见的响应状态码包括 200(成功)、201(已创建)、400(参数错误)、401(未授权)、403(禁止访问)、404(资源不存在)以及 500(服务器内部错误)。
Spring Boot RESTful API 的开发
在 Spring Boot 中开发 RESTful API 通常遵循标准的分层架构:Controller -> Service -> Repository。下面我们以一个商品管理模块为例,展示完整的开发步骤。
1. 项目依赖配置
首先需要在 pom.xml 中添加 Web、JPA 和测试相关的依赖。
<dependencies >
<dependency >
<groupId > org.springframework.boot</groupId >
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
com.h2database
h2
runtime
org.springframework.boot
spring-boot-starter-test
test
<artifactId >
</artifactId >
</dependency >
<dependency >
<groupId >
</groupId >
<artifactId >
</artifactId >
</dependency >
<dependency >
<groupId >
</groupId >
<artifactId >
</artifactId >
<scope >
</scope >
</dependency >
<dependency >
<groupId >
</groupId >
<artifactId >
</artifactId >
<scope >
</scope >
</dependency >
</dependencies >
2. 实体类定义 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 +
'}' ;
}
}
3. 数据访问层(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) ;
}
4. 业务逻辑层(Service) 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;
}
}
5. 控制层(Controller) 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 RESTful API 的测试 测试是保证代码质量的关键。Spring Boot 提供了丰富的测试支持,主要分为单元测试、集成测试和 Mock 测试。
单元测试 单元测试关注单个方法或类的功能,通常不需要启动整个 Spring 容器。但在 Spring Boot 中,我们常使用 @SpringBootTest 来加载上下文进行服务层测试。
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 );
}
@Test
void testUpdateProduct () {
Product product = new Product ("P001" , "手机" , 1500.0 , 120 );
product.setId(1L );
productService.updateProduct(product);
List<Product> products = productService.getAllProducts();
assertThat(products.get(0 ).getPrice()).isEqualTo(1500.0 );
}
}
集成测试 集成测试验证多个组件之间的交互,通常需要启动真实的 HTTP 服务器。使用 WebEnvironment.RANDOM_PORT 配合 TestRestTemplate 可以方便地模拟 HTTP 请求。
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 );
}
@Test
void testAddProduct () {
Product product = new Product ("P006" , "平板" , 2000.0 , 70 );
restTemplate.postForEntity(
"http://localhost:" + port + "/api/products/" , product, Void.class);
List<Product> products = restTemplate.getForObject(
"http://localhost:" + port + "/api/products/" , List.class);
assertThat(products).hasSize(6 );
}
}
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();
}
}
Spring Boot RESTful API 的认证与授权 生产环境的 API 必须考虑安全性,Spring Security 是最常用的解决方案。
Spring Security 基础 通过配置类定义用户认证规则和权限控制。这里演示了基于 HTTP Basic 的简单认证方式。
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 访问后续接口。
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 extractUsername (String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration (String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim (String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims (String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired (String token) {
return extractExpiration(token).before(new Date ());
}
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));
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization" );
String username = null ;
String jwt = null ;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer " )) {
jwt = authorizationHeader.substring(7 );
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null ) {
UserDetails userDetails = this .userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails.getUsername())) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken (userDetails, null , userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource ().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
# 服务器端口
server.port=8080
# 数据库连接信息
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
# JPA 配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
# H2 数据库控制台
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# JWT 配置
jwt.secret=mysecret
jwt.expiration=3600
Spring Boot RESTful API 的实际应用场景 在实际项目中,RESTful API 的应用非常广泛,例如商品展示、订单管理、用户中心或博客系统。通过上述的分层设计和安全机制,我们可以快速构建出稳定、可扩展的后端服务。
运行应用后,访问 http://localhost:8080/api/products/ 即可获取产品列表,而 /api/products/top-selling?topN=3 则返回销量前三的商品。结合 JWT 认证,非授权用户将无法访问敏感接口,确保数据安全。
总结 本章我们梳理了 Spring Boot RESTful API 从设计到落地的全过程。掌握了 RESTful 的核心规范,熟悉了 Spring Boot 的标准开发流程,并深入理解了三种测试模式的区别。特别是在安全方面,对比了 Spring Security 的基础认证与 JWT 无状态认证的适用场景。这些知识构成了构建企业级后端服务的基石。
相关免费在线工具 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