springboot
RESTful API 与 Web 开发
By AI-Writer 11 min read
RESTful API 与 Web 开发
Spring MVC 是构建 Web 应用的基石,而 RESTful API 是现代前后端分离架构的标准接口形式。本篇文章将系统讲解从控制器注解到参数绑定的全部核心技能,并涵盖参数校验与统一响应封装。
控制器注解体系
@RestController vs @Controller
java
// @RestController = @Controller + @ResponseBody
// 返回值直接写入 HTTP 响应体(JSON/XML)
@RestController
@RequestMapping("/api/users")
public class UserApiController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// 返回 User 对象,Spring 自动序列化为 JSON
return userService.findById(id);
}
}java
// 如果需要返回视图(Thymeleaf/Freemarker),使用 @Controller
@Controller
@RequestMapping("/users")
public class UserViewController {
@GetMapping("/list")
public String listPage(Model model) {
model.addAttribute("users", userService.findAll());
return "user/list"; // 返回视图名,由视图解析器渲染模板
}
// 如果 @Controller 中某个方法需要返回 JSON,使用 @ResponseBody
@GetMapping("/api/list")
@ResponseBody
public List<User> listApi() {
return userService.findAll();
}
}请求映射 @RequestMapping 系列
@RequestMapping 是最通用的映射注解,Spring 提供了更语义化的快捷方式:
java
@RestController
@RequestMapping("/api/users") // 类级别:所有方法共享的前缀
public class UserController {
// @GetMapping 是 @RequestMapping(method = RequestMethod.GET) 的快捷写法
@GetMapping // 查询列表
public List<User> list() { ... }
@GetMapping("/{id}") // 查询单个
public User get(@PathVariable Long id) { ... }
@PostMapping // 创建
public User create(@RequestBody @Valid CreateUserRequest request) { ... }
@PutMapping("/{id}") // 完整更新
public User update(@PathVariable Long id,
@RequestBody @Valid UpdateUserRequest request) { ... }
@PatchMapping("/{id}") // 部分更新
public User patch(@PathVariable Long id,
@RequestBody Map<String, Object> fields) { ... }
@DeleteMapping("/{id}") // 删除
public void delete(@PathVariable Long id) { ... }
}映射变体速查
java
// Ant 风格路径(Spring MVC 支持)
@GetMapping("/api/**/users") // 匹配 /api/aaa/users、/api/bbb/users
@GetMapping("/api/users/*.json") // 匹配 /api/users/1.json
@GetMapping("/api/users/?") // 匹配 /api/users/1、/api/users/2
// PathVariable 支持正则(Spring MVC 支持)
@GetMapping("/api/users/{id:\\d+}") // 仅匹配数字 ID
@GetMapping("/api/users/{name:[a-z]+}") // 仅匹配小写字母 name
// 请求头 / Content-Type 条件映射
@GetMapping(value = "/data", produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
// 请求头条件映射
@GetMapping(value = "/preview", headers = "X-Preview=true")
public String preview() { ... }参数绑定
@PathVariable — 路径变量
java
// 单个路径变量
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
// @PathVariable("id") 或省略名称(自动推断)
return userService.findById(id);
}
// 多个路径变量
@GetMapping("/users/{userId}/orders/{orderId}")
public Order getOrder(
@PathVariable Long userId,
@PathVariable Long orderId) {
return orderService.findByUserAndOrder(userId, orderId);
}
// 路径变量重命名(适配 snake_case URL)
@GetMapping("/users/{user-id}")
public User getByUserId(@PathVariable("user-id") Long id) {
// 访问 /users/123 -> id = 123
return userService.findById(id);
}@RequestParam — 查询参数
java
// ?page=0&size=20&sort=createdAt,desc
@GetMapping("/users")
public Page<User> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String sort,
@RequestParam(required = false) String keyword) {
return userService.search(keyword, page, size, sort);
}
// 多值参数(?tags=spring&tags=java)
@GetMapping("/articles")
public List<Article> search(@RequestParam List<String> tags) {
return articleService.findByTagsIn(tags);
}
// JSON 请求体(POST / PUT)
@PostMapping("/users")
public User create(@RequestBody CreateUserRequest request) {
return userService.create(request);
}@RequestBody — 请求体自动反序列化
Spring Boot 默认使用 Jackson 将 JSON 反序列化为 Java 对象:
java
// 创建请求 DTO(使用 record,Spring Boot 3 推荐)
public record CreateUserRequest(
String username,
String email,
@Email(message = "邮箱格式不正确") String emailField,
Integer age
) {}
// @Valid 触发 Jakarta Bean Validation(Spring Boot Starter Validation 已自动配置)
@PostMapping("/users")
public User create(@RequestBody @Valid CreateUserRequest request,
BindingResult result) {
if (result.hasErrors()) {
throw new ValidationException(result.getFieldErrors()
.stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.joining("; ")));
}
return userService.create(request);
}@RequestAttribute 与 @SessionAttribute
在非同步请求或拦截器场景中获取请求属性和会话属性:
java
@GetMapping("/profile")
public UserProfile profile(
@RequestAttribute("tenantId") String tenantId,
@SessionAttribute("userId") Long userId,
@CookieValue(value = "SESSION", required = false) String sessionId) {
return userService.getProfile(tenantId, userId);
}REST 风格 URL 设计规范
良好的 REST API 设计遵循以下原则:
plaintext
# 资源命名:使用复数名词,名词而非动词
✅ GET /api/users # 用户列表
✅ GET /api/users/123 # 单个用户
✅ POST /api/users # 创建用户
✅ PUT /api/users/123 # 完整更新用户
✅ PATCH /api/users/123 # 部分更新用户
✅ DELETE /api/users/123 # 删除用户
❌ GET /getUser?id=123 # 避免动词
❌ POST /createUser # 避免动词
❌ GET /api/getAllUsers # 避免动词嵌套资源反映资源间的从属关系:
plaintext
GET /api/users/123/orders # 用户123的所有订单
GET /api/users/123/orders/456 # 用户123的订单456
POST /api/users/123/orders # 为用户123创建订单
DELETE /api/users/123/orders/456 # 删除用户123的订单456统一响应封装
每个 API 返回统一的响应结构,便于前端处理:
java
// 响应封装类(使用 record)
public record ApiResponse<T>(
int code,
String message,
T data,
long timestamp
) {
// 静态工厂方法
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "OK", data, System.currentTimeMillis());
}
public static <T> ApiResponse<T> created(T data) {
return new ApiResponse<>(201, "Created", data, System.currentTimeMillis());
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null, System.currentTimeMillis());
}
}
// 全局异常处理(@ControllerAdvice)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ApiResponse<Void> handleNotFound(UserNotFoundException ex) {
return ApiResponse.error(404, ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Map<String, String>> handleValidation(MethodArgumentNotValidException ex) {
Map<String, String> errors = ex.getBindingResult().getFieldErrors()
.stream()
.collect(Collectors.toMap(
FieldError::getField,
e -> e.getDefaultMessage() != null ? e.getDefaultMessage() : "Invalid value",
(a, b) -> a
));
return new ApiResponse<>(400, "Validation failed", errors, System.currentTimeMillis());
}
}控制器改写:
java
@PostMapping
public ResponseEntity<ApiResponse<User>> create(
@Valid @RequestBody CreateUserRequest request) {
User user = userService.create(request);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(ApiResponse.created(user));
}@Validated 参数校验
Spring Boot 已自动配置 Hibernate Validator(Jakarta Bean Validation 的实现),无需额外依赖。
常用校验注解
java
public record CreateUserRequest(
@NotBlank(message = "用户名不能为空") // 非空字符串
@Size(min = 3, max = 20, message = "用户名3-20位")
String username,
@NotBlank
@Email(message = "邮箱格式不正确")
String email,
@NotNull(message = "年龄不能为空")
@Min(value = 0, message = "年龄不能为负数")
@Max(value = 150, message = "年龄不合理")
Integer age,
@NotBlank
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
String phone,
@NotBlank
@Size(min = 6, max = 20)
String password,
// 分组校验:不同场景用不同分组
@NotNull(groups = {UpdateGroup.class})
Long id
) {}分组校验
java
// 定义校验分组
public interface UpdateGroup {}
public interface CreateGroup {}
// 请求对象使用分组
public record UpdateUserRequest(
@NotNull(groups = UpdateGroup.class) Long id,
@NotBlank(groups = {CreateGroup.class, UpdateGroup.class}) String name
) {}
// 控制器中指定分组
@PostMapping
public void create(@Validated(CreateGroup.class) @RequestBody UpdateUserRequest req) { }
@PutMapping
public void update(@Validated(UpdateGroup.class) @RequestBody UpdateUserRequest req) { }自定义校验注解
java
// 1. 定义注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValidator.class)
@NotNull
public @interface EnumValue {
String message() default "值不在允许范围内";
Class<? extends Enum<?>[]> enumClass();
String method() default "name";
}
// 2. 实现校验器
public class EnumValidator implements ConstraintValidator<EnumValue, String> {
private Enum<?>[] enumConstants;
@Override
public void initialize(EnumValue annotation) {
enumConstants = annotation.enumClass().getEnumConstants();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) return true; // @NotNull 负责空值校验
return Arrays.stream(enumConstants)
.anyMatch(e -> e.name().equals(value));
}
}
// 3. 使用
public record CreateArticleRequest(
@EnumValue(enumClass = Category.class) String category
) {}小结
@RestController=@Controller+@ResponseBody,适合前后端分离的 REST API@GetMapping / @PostMapping / @PutMapping / @DeleteMapping / @PatchMapping是标准 HTTP 方法映射@PathVariable绑定 URL 路径变量,@RequestParam绑定查询参数,@RequestBody绑定请求体 JSON- REST API 命名使用复数名词和标准 HTTP 方法,避免在 URL 中使用动词
- 使用
@Validated+ Jakarta Bean Validation 注解实现声明式参数校验 @ControllerAdvice+@ExceptionHandler实现全局统一异常处理和响应封装
下一篇文章我们将学习 数据访问:JPA 与 MyBatis,掌握持久层的完整开发技能。
#springboot
#rest-api
#spring-mvc
#validation
评论
A
Written by
AI-Writer
Related Articles
springboot
#2 Spring Boot 自动配置原理
深入理解 @SpringBootApplication 注解组合、自动配置生效机制(spring.factories 与 AutoConfiguration.imports)、自定义 Starter 的编写方法
Read More springboot
#11 Spring Boot 3 监控、可观测性与性能调优
Spring Boot Actuator 端点、Micrometer + Prometheus + Grafana 可观测性栈集成、自定义指标、JVM 参数调优与 HikariCP 连接池优化
Read More springboot
#9 Docker 容器化与生产部署
Spring Boot fat jar 原理、分层 Docker 镜像构建(layered jar)、docker-compose 多容器编排、GitHub Actions CI/CD 流水线与生产环境配置示例
Read More