跳到主要内容
Spring Boot RESTful API 开发实战与测试指南 | 极客日志
Java java
Spring Boot RESTful API 开发实战与测试指南 综述由AI生成 Spring Boot RESTful API 开发实战涵盖了从基础概念到安全认证的全流程。文章详细讲解了 RESTful 架构风格的核心要素,包括资源定位与 HTTP 方法的使用。通过完整的项目示例,演示了如何构建实体类、Repository、Service 及 Controller 层。测试部分深入探讨了单元测试、集成测试与 Mock 测试的区别与应用场景。此外,还对比了 Spring Security 基础认证与 JWT 无状态认证的实现方式,帮助开发者在实际项目中建立健壮的 API 服务。
筑梦师 发布于 2026/3/28 更新于 2026/4/24 1 浏览Spring Boot RESTful API 开发实战与测试指南
核心目标与重点
在 Java 后端开发中,RESTful API 是构建 Web 服务的主流方式。本章我们将深入掌握其核心概念与使用方法,包括 RESTful 的定义与特点、基于 Spring Boot 的开发流程、多层次的测试策略(单元、集成、Mock),以及认证授权机制(Spring Security 与 JWT)。重点在于理解资源定位、表现层状态转移,并能熟练运用 @RestController、@RequestMapping 等注解进行实际开发。
RESTful API 概述
RESTful API 是一种基于 REST 架构风格的 API 设计,旨在实现 Web 应用的标准化交互。它通过 URI 表示资源,利用 HTTP 请求方法(GET、POST、PUT、DELETE、PATCH)来操作资源,并使用 HTTP 响应状态码反馈结果。这种设计提高了开发效率,提供了统一的编程模型。
常用的 HTTP 方法对应不同的业务动作:
GET:获取资源。
POST:创建资源。
PUT:更新资源。
DELETE:删除资源。
PATCH:部分更新资源。
对应的响应状态码也需规范使用,例如 200 表示成功,201 表示资源创建成功,400 表示参数错误,401/403 涉及权限问题,500 则是服务器内部错误。
Spring Boot RESTful API 开发
开发一个标准的 Spring Boot RESTful API 通常遵循分层架构。以下是具体步骤和关键代码示例。
1. 项目依赖配置
首先需要在 pom.xml 中添加必要的依赖,包括 Web 启动器、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 >
2. 实体类定义 实体类映射数据库表,这里以商品(Product)为例。注意字段类型和 Getter/Setter 方法的完整性。
import javax.persistence.*;
import java.util.Objects;
@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 层逻辑 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 层接口 Controller 负责接收 HTTP 请求并返回响应。使用 @RestController 配合 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 RESTful API 的测试 测试是保证代码质量的关键。我们通常从单元测试开始,逐步扩展到集成测试和 Mock 测试。
单元测试 单元测试主要关注单个 Service 方法的功能,不依赖外部容器或网络。
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 );
}
@Test
void testDeleteProduct () {
productService.deleteProduct(2L );
List<Product> products = productService.getAllProducts();
assertThat(products).hasSize(4 );
}
@Test
void testGetTopSellingProducts () {
List<Product> topSellingProducts = productService.getTopSellingProducts(3 );
assertThat(topSellingProducts).hasSize(3 );
assertThat(topSellingProducts.get(0 ).getProductId()).isEqualTo("P004" );
}
}
集成测试 集成测试需要启动完整的 Web 环境,模拟真实的 HTTP 请求。使用 TestRestTemplate 是最便捷的方式。
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 );
}
@Test
void testUpdateProduct () {
Product product = new Product ("P001" , "手机" , 1500.0 , 120 );
restTemplate.put("http://localhost:" + port + "/api/products/1" , product);
List<Product> products = restTemplate.getForObject("http://localhost:" + port + "/api/products/" , List.class);
assertThat(products.get(0 ).getPrice()).isEqualTo(1500.0 );
}
@Test
void testDeleteProduct () {
restTemplate.delete("http://localhost:" + port + "/api/products/2" );
List<Product> products = restTemplate.getForObject("http://localhost:" + port + "/api/products/" , List.class);
assertThat(products).hasSize(4 );
}
}
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();
}
@Test
void testAddProduct () throws Exception {
Product product = new Product ("P006" , "平板" , 2000.0 , 70 );
doNothing().when (productService).addProduct(any(Product.class));
mockMvc.perform(post("/api/products/" ).contentType(MediaType.APPLICATION_JSON)
.content("{\"productId\":\"P006\",\"productName\":\"平板\",\"price\":2000.0,\"sales\":70}" ))
.andExpect(status().isCreated());
verify(productService, times(1 )).addProduct(any(Product.class));
}
}
Spring Boot RESTful API 的认证与授权 生产环境的 API 必须考虑安全性。Spring Boot 提供了强大的安全框架支持。
Spring Security 基础认证 对于简单的内部系统,HTTP Basic Auth 可能足够。配置类中定义用户信息和权限规则。
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)是更常见的选择。它允许服务端在无会话状态下验证用户身份。
<dependency >
<groupId > io.jsonwebtoken</groupId >
<artifactId > jjwt</artifactId >
<version > 0.9.1</version >
</dependency >
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);
}
}
# application.properties
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
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
jwt.secret=mysecret
jwt.expiration=3600
实际应用场景总结 在实际项目中,RESTful API 广泛应用于商品展示、订单管理、用户中心及内容发布等场景。通过上述的分层架构、测试策略及安全配置,我们可以构建出高可用、易维护的后端服务。开发者应根据具体需求选择合适的认证方式(如 Session 或 JWT),并确保测试覆盖率达到预期标准。
从下一章开始,我们将进一步探索 Spring Boot 的其他组件及微服务架构的相关内容。
相关免费在线工具 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