Spring Boot RESTful API开发
文章目录
- [Spring Boot RESTful API开发](#Spring Boot RESTful API开发)
-
- 前言
- [一、RESTful API设计规范](#一、RESTful API设计规范)
-
- [1.1 什么是RESTful](#1.1 什么是RESTful)
- [1.2 URL与HTTP方法设计](#1.2 URL与HTTP方法设计)
- [1.3 命名规范](#1.3 命名规范)
- 二、Controller注解详解
-
- [2.1 @RestController与@Controller](#2.1 @RestController与@Controller)
- [2.2 请求映射注解](#2.2 请求映射注解)
- [2.3 参数获取方式](#2.3 参数获取方式)
- 三、参数校验
-
- [3.1 引入依赖](#3.1 引入依赖)
- [3.2 实体类校验注解](#3.2 实体类校验注解)
- [3.3 Controller校验](#3.3 Controller校验)
- [3.4 常用校验注解](#3.4 常用校验注解)
- [3.5 自定义校验注解](#3.5 自定义校验注解)
- 四、统一返回格式
-
- [4.1 通用返回类](#4.1 通用返回类)
- [4.2 状态码枚举](#4.2 状态码枚举)
- [4.3 在Controller中使用](#4.3 在Controller中使用)
- 五、全局异常处理
-
- [5.1 自定义业务异常](#5.1 自定义业务异常)
- [5.2 全局异常处理器](#5.2 全局异常处理器)
- [5.3 使用业务异常](#5.3 使用业务异常)
- 六、跨域配置
- 七、接口文档
- 总结
- [✅ 亮点总结](#✅ 亮点总结)
- 适用场景
- 扩展方向
前言
在现代Web开发中,RESTful API已成为前后端分离架构的标准通信方式。Spring Boot提供了强大的注解和工具支持,让开发者可以轻松构建规范的RESTful接口。
为什么RESTful很重要? 在前后端分离的架构中,后端只负责提供API接口、返回JSON数据,前端(Vue/React/小程序)通过HTTP请求获取数据并渲染页面。如果没有统一的API设计规范,每个人的接口风格都不一样------有人用GET创建资源、有人用POST查询数据------前后端联调时会耗费大量时间在沟通上。RESTful规范提供了一套统一的设计准则,让API"可预测"。在面试中,RESTful设计原则、HTTP方法语义、统一返回格式等也是常见考点。
本文将详细介绍Controller层的开发技巧,包括参数校验、统一返回格式、全局异常处理等实战内容,帮你构建健壮、规范的API。
一、RESTful API设计规范
1.1 什么是RESTful
REST(Representational State Transfer) 是一种软件架构风格,核心思想是:将服务器上的所有资源都抽象为URL,通过HTTP方法对资源进行增删改查操作。
1.2 URL与HTTP方法设计
以用户管理为例,标准的RESTful API设计如下:
HTTP方法的语义化使用:
- GET:获取资源,幂等且安全(多次请求结果相同,不会修改资源)
- POST:创建资源,不幂等(多次请求会创建多个资源)
- PUT:全量更新资源,幂等(多次请求结果相同)
- PATCH:部分更新资源,不保证幂等
- DELETE:删除资源,幂等
面试中经常问到的"GET和POST的区别"在RESTful语境下主要是语义不同:GET用于读取,POST用于创建。技术上GET的请求参数在URL中(有长度限制),POST的请求数据在请求体中(无长度限制)。
| 功能 | 方法 | URL | 请求体 |
|---|---|---|---|
| 查询所有用户 | GET | /api/users | 无 |
| 查询单个用户 | GET | /api/users/{id} | 无 |
| 新增用户 | POST | /api/users | 用户JSON |
| 更新用户 | PUT | /api/users/{id} | 用户JSON |
| 部分更新 | PATCH | /api/users/{id} | 部分字段JSON |
| 删除用户 | DELETE | /api/users/{id} | 无 |
1.3 命名规范
好的示例:
GET /api/users # 获取用户列表
GET /api/users/1 # 获取ID为1的用户
GET /api/users/1/orders # 获取用户1的订单
POST /api/users # 新增用户
DELETE /api/users/1 # 删除用户
不好的示例:
GET /api/getUsers # 不要在URL中使用动词
POST /api/addUser # 动词由HTTP方法表达
GET /api/user_list # 使用复数名词
二、Controller注解详解
2.1 @RestController与@Controller
java
// @Controller:返回视图名,配合@ResponseBody使用
@Controller
public class UserController {
@ResponseBody
@GetMapping("/users")
public List<User> list() {
return userService.listAll();
}
}
// @RestController = @Controller + @ResponseBody(推荐)
@RestController
@RequestMapping("/api/users")
public class UserController {
// 所有方法返回值都会被序列化为JSON
}
2.2 请求映射注解
java
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping // GET /api/users
public List<User> list() {}
@GetMapping("/{id}") // GET /api/users/{id}
public User getById(@PathVariable Long id) {}
@PostMapping // POST /api/users
public User create(@RequestBody User user) {}
@PutMapping("/{id}") // PUT /api/users/{id}
public User update(@PathVariable Long id, @RequestBody User user) {}
@DeleteMapping("/{id}") // DELETE /api/users/{id}
public void delete(@PathVariable Long id) {}
}
2.3 参数获取方式
java
@RestController
@RequestMapping("/api/users")
public class UserController {
// 路径参数:/api/users/5
@GetMapping("/{id}")
public User getById(@PathVariable("id") Long userId) {}
// 查询参数:/api/users?name=张三&page=1&size=10
@GetMapping
public List<User> list(
@RequestParam(required = false) String name,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {}
// 请求体参数(JSON)
@PostMapping
public User create(@RequestBody @Validated User user) {}
// 请求头参数
@GetMapping("/auth")
public String auth(@RequestHeader("Authorization") String token) {}
// HttpservletRequest原生对象
@GetMapping("/info")
public String info(HttpServletRequest request) {
return request.getRemoteAddr();
}
}
三、参数校验
3.1 引入依赖
为什么需要服务端校验? 前端可以做表单验证,但前端验证可以被绕过(通过Postman、curl等工具直接发送请求)。服务端校验是最后一道防线,必须做到位。Spring Boot Validation基于JSR 380(Bean Validation 2.0)标准,通过注解即可实现声明式参数校验,比手写if-else检查每个字段要简洁和规范得多。
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
3.2 实体类校验注解
java
import javax.validation.constraints.*;
import lombok.Data;
@Data
public class UserCreateRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度需在2~20之间")
private String username;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotNull(message = "年龄不能为空")
@Min(value = 1, message = "年龄不能小于1")
@Max(value = 150, message = "年龄不能大于150")
private Integer age;
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}
3.3 Controller校验
java
@PostMapping
public Result<User> create(@Validated @RequestBody UserCreateRequest request) {
// 通过@Validated触发校验,校验失败会自动抛出MethodArgumentNotValidException
User user = new User();
BeanUtils.copyProperties(request, user);
userService.create(user);
return Result.success(user);
}
3.4 常用校验注解
| 注解 | 说明 |
|---|---|
| @NotNull | 不能为null |
| @NotEmpty | 不能为null且长度>0(字符串/集合) |
| @NotBlank | 不能为null且trim后长度>0(字符串) |
| @Size(min, max) | 字符串/集合长度范围 |
| @Min / @Max | 数值最小/最大值 |
| 邮箱格式 | |
| @Pattern | 正则表达式匹配 |
| @Positive / @Negative | 正数/负数 |
| @Past / @Future | 过去/未来日期 |
3.5 自定义校验注解
java
// 自定义注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {StatusValidator.class})
public @interface ValidStatus {
String message() default "状态值不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] allowedValues() default {};
}
// 校验器实现
public class StatusValidator implements ConstraintValidator<ValidStatus, Integer> {
private int[] allowedValues;
@Override
public void initialize(ValidStatus annotation) {
this.allowedValues = annotation.allowedValues();
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (value == null) return true; // null由@NotNull处理
for (int v : allowedValues) {
if (v == value) return true;
}
return false;
}
}
// 使用
@ValidStatus(allowedValues = {0, 1, 2}, message = "状态值只能是0、1、2")
private Integer status;
四、统一返回格式
前后端分离项目中,统一的API返回格式便于前端统一处理响应和异常。
为什么需要统一返回格式? 如果没有统一的返回格式,每个接口的返回值格式可能都不一样------有的是直接返回对象,有的是返回Map,出错时的响应格式更是五花八门。前端需要为每种情况编写不同的处理逻辑,代码变得臃肿且易出错。统一返回格式让前端可以只需要一个拦截器(如axios的response拦截器)就能处理所有接口的成功和失败情况,大大简化了前端代码。
4.1 通用返回类
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private int code;
private String message;
private T data;
private long timestamp;
// 成功响应
public static <T> Result<T> success(T data) {
return new Result<>(200, "success", data, System.currentTimeMillis());
}
public static <T> Result<T> success() {
return success(null);
}
// 失败响应
public static <T> Result<T> error(int code, String message) {
return new Result<>(code, message, null, System.currentTimeMillis());
}
public static <T> Result<T> error(String message) {
return error(500, message);
}
}
4.2 状态码枚举
java
public enum ResultCode {
SUCCESS(200, "操作成功"),
BAD_REQUEST(400, "请求参数错误"),
UNAUTHORIZED(401, "未授权"),
FORBIDDEN(403, "无权限"),
NOT_FOUND(404, "资源不存在"),
INTERNAL_ERROR(500, "服务器内部错误");
private final int code;
private final String message;
ResultCode(int code, String message) {
this.code = code;
this.message = message;
}
// getter方法省略
}
4.3 在Controller中使用
java
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public Result<List<User>> list() {
List<User> users = userService.listAll();
return Result.success(users);
}
@PostMapping
public Result<User> create(@Validated @RequestBody UserCreateRequest request) {
User user = userService.create(request);
return Result.success(user);
}
@DeleteMapping("/{id}")
public Result<Void> delete(@PathVariable Long id) {
userService.delete(id);
return Result.success();
}
}
五、全局异常处理
5.1 自定义业务异常
为什么需要全局异常处理? 在一个没有全局异常处理的项目中,异常处理代码散落在各个Controller方法中,到处都是try-catch,代码冗余且容易遗漏。更糟糕的是,未被捕获的异常会暴露给前端------用户可能看到500错误页面、堆栈信息等,既不友好也不安全。全局异常处理器通过@RestControllerAdvice统一拦截所有异常,根据异常类型返回规范的错误响应,让Controller代码保持干净。
java
public class BusinessException extends RuntimeException {
private int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public BusinessException(String message) {
super(message);
this.code = 500;
}
public BusinessException(ResultCode resultCode) {
super(resultCode.getMessage());
this.code = resultCode.getCode();
}
// getter省略
}
5.2 全局异常处理器
java
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidationException(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldErrors()
.stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining("; "));
return Result.error(400, message);
}
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException ex) {
return Result.error(ex.getCode(), ex.getMessage());
}
// 处理运行时异常
@ExceptionHandler(RuntimeException.class)
public Result<Void> handleRuntimeException(RuntimeException ex) {
// 生产环境建议只返回"服务器内部错误",详细信息记日志
return Result.error(500, "服务器内部错误: " + ex.getMessage());
}
// 处理所有未被捕获的异常
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception ex) {
return Result.error(500, "系统异常,请联系管理员");
}
}
异常处理的优先级:Spring会按照@ExceptionHandler中声明的异常类型从具体到泛化进行匹配。如果你同时定义了BusinessException的处理器和RuntimeException的处理器,当抛出BusinessException时,会优先匹配BusinessException的处理器。这就是为什么要把具体的异常处理器放在前面、通用的异常处理器放在后面------确保每个异常都被最合适的处理器处理。建议顺序:自定义业务异常 → 参数校验异常 → 运行时异常 → Exception兜底。
5.3 使用业务异常
java
@Service
public class UserService {
public User getById(Long id) {
User user = userMapper.findById(id);
if (user == null) {
throw new BusinessException(ResultCode.NOT_FOUND);
}
return user;
}
}
六、跨域配置
前后端分离开发中,需要处理CORS跨域问题:
java
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 允许跨域的路径
.allowedOriginPatterns("*") // 允许的来源
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
.allowedHeaders("*") // 允许的请求头
.allowCredentials(true) // 是否允许携带Cookie
.maxAge(3600); // 预检请求缓存时间
}
}
或者使用 @CrossOrigin 注解(适合少数接口):
java
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "http://localhost:3000")
public class UserController {}
七、接口文档
推荐使用Swagger(SpringDoc)自动生成接口文档:
xml
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
java
@Operation(summary = "查询用户列表", description = "支持分页和条件筛选")
@GetMapping
public Result<PageInfo<User>> list(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") int page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") int size) {
// ...
}
项目启动后访问 http://localhost:8080/swagger-ui.html 即可看到接口文档。
总结
本文详细介绍了Spring Boot开发RESTful API的核心内容:URL设计规范、常用注解、参数校验、统一返回格式和全局异常处理。一个规范的API应该具备以下特质:URL命名语义化 、参数校验完备 、返回格式统一 、异常处理友好。掌握这些技能,你就能构建出前后端协作顺畅的API服务了。
RESTful API开发核心清单:
- URL设计:名词复数、层级关系、HTTP方法语义化
- 参数获取:@PathVariable(路径参数)、@RequestParam(查询参数)、@RequestBody(JSON请求体)、@RequestHeader(请求头)
- 参数校验:@Validated + Bean Validation注解链,必要时自定义校验器
- 统一返回:Result<T>(code + message + data + timestamp)
- 异常处理:@RestControllerAdvice分层捕获,业务异常→校验异常→运行时异常→兜底异常
- 跨域配置:CorsConfig + WebMvcConfigurer 或 @CrossOrigin注解
✅ 亮点总结
- RESTful URL规范:名词复数、HTTP方法语义化(GET查/POST增/PUT改/DELETE删)
- @Validated + 校验注解(@NotBlank/@Email/@Pattern等)实现声明式参数校验
- 统一返回格式Result<T>:封装code/message/data/timestamp,前端统一处理
- @RestControllerAdvice全局异常处理:分类捕获参数校验异常、业务异常、系统异常
- CORS跨域配置 + Swagger/SpringDoc接口文档自动生成,提升前后端协作效率
适用场景
- 前后端分离项目:Vue/React前端通过Axios调用RESTful API获取JSON数据
- 移动端后端服务:Android/iOS App通过网络请求调用统一格式的API接口
- 开放平台API设计:对外提供规范的RESTful接口,配合Swagger文档方便第三方接入
扩展方向
- 学习Spring Security + JWT实现无状态认证和接口鉴权
- 了解OpenAPI 3.0规范,使用SpringDoc生成标准化的接口文档
- 推荐阅读下一篇文章:MySQL基础与JDBC操作,夯实数据库底层知识