跳到主要内容
Spring Boot RESTful API 开发全流程与测试实战 | 极客日志
Java java
Spring Boot RESTful API 开发全流程与测试实战 Spring Boot RESTful API 开发涉及资源导向设计、分层架构实现及多维度测试策略。了基于 JPA 的 CRUD 接口构建,涵盖 Entity、Repository、Service 及 Controller 层代码规范。测试部分对比了单元测试、集成测试与 Mock 测试的适用场景,分别展示了 TestRestTemplate 与 MockMvc 的实战用法。安全方面介绍了 Spring Security 的 Basic Auth 配置及 JWT 无状态认证流程,包含 Token 生成、校验及过滤器实现。通过实际案例帮助开发者掌握从开发到部署的关键环节。
怪力乱神 发布于 2026/3/22 更新于 2026/9/9 56 浏览Spring Boot RESTful API 开发全流程与测试实战
RESTful API 是 Java 后端开发中最主流的设计风格。在 Spring Boot 生态中,构建一个健壮的 RESTful 服务不仅涉及接口定义,更关乎测试策略与安全控制。本文将结合实战代码,带你梳理从设计、开发到测试、认证的全流程。
RESTful 设计核心
RESTful 的核心在于资源导向(Resource-Oriented)。我们使用 URI 来标识资源,通过 HTTP 动词(GET、POST、PUT、DELETE)来表达操作意图,并用状态码反馈执行结果。
常用的 HTTP 方法包括:
GET :获取资源
POST :创建资源
PUT :更新资源
DELETE :删除资源
PATCH :部分更新资源
响应状态码同样关键,例如 200 表示成功,201 代表创建成功,400 为参数错误,401/403 涉及权限问题,而 500 则是服务器内部异常。理解这些规范,能让你的 API 更加符合直觉且易于维护。
开发实战:从实体到控制器
搭建一个标准的 Spring Boot RESTful 服务,通常遵循分层架构。我们以一个简单的商品管理为例,涵盖 Entity、Repository、Service 和 Controller 四个层级。
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. 实体层 (Entity) 定义数据模型,这里以 Product 为例。注意注解的使用和 Getter/Setter 的生成。
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; }
}
3. 数据访问层 (Repository) 利用 Spring Data JPA 简化 SQL 操作,甚至可以自定义查询方法。
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) 处理事务和业务规则,Controller 层不应直接操作数据库。
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) 暴露 HTTP 接口,使用 @RestController 配合路径映射注解。
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);
}
}
测试策略:单元、集成与 Mock 代码写得再好,没有测试也是空中楼阁。Spring Boot 提供了丰富的测试支持,针对不同场景选择合适的测试方式至关重要。
单元测试 专注于 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 );
}
}
集成测试 需要启动完整的 Web 环境,模拟真实的 HTTP 请求,适合验证 Controller 与外部交互。
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 测试 当需要隔离依赖(如数据库或第三方服务)时,使用 MockMvc 配合 @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 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 {
when (productService.getAllProducts()).thenReturn(Arrays.asList());
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 基础认证 对于内部系统或简单场景,Basic Auth 足够使用。配置类中定义用户信息和权限规则。
import org.springframework.context.annotation.Configuration;
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 (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)更为常见。它允许客户端携带凭证,服务端无需存储 Session。
核心流程包括:生成 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;
@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));
}
private String extractUsername (String token) {
return extractClaim(token, Claims::getSubject);
}
private Boolean isTokenExpired (String token) {
return extractExpiration(token).before(new Date ());
}
private Claims extractAllClaims (String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private <T> T extractClaim (String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Date extractExpiration (String token) {
return extractClaim(token, Claims::getExpiration);
}
}
配合 JwtRequestFilter 拦截请求并设置 SecurityContext,即可实现基于 Token 的鉴权。配置文件需指定密钥和过期时间。
# application.properties
jwt.secret=mysecret
jwt.expiration=3600
总结 通过上述内容,我们完成了 Spring Boot RESTful API 从设计到落地的全过程。重点掌握了资源导向的设计理念、分层开发的代码结构、三种不同粒度的测试方法,以及基于 Spring Security 的安全防护方案。在实际项目中,根据业务复杂度灵活组合这些技术点,才能构建出既高效又安全的后端服务。
相关免费在线工具 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