完整示例代码结构
src/main/java/com.example.demo/
├── controller/
│ └── UserController.java
├── dto/
│ ├── UserQuery.java
│ ├── User.java
│ └── Result.java
├── exception/
│ ├── BusinessException.java
│ ├── ErrorCode.java
│ └── GlobalExceptionHandler.java
└── service/
└── UserService.java
一、请求参数接收
Spring Boot 基于 Spring MVC,提供了丰富的注解来绑定 HTTP 请求中的不同数据源(URL 路径、查询参数、表单、请求体等)。以下按参数类型分类讲解。
1. 简单参数(基本类型 / 包装类 / String)
场景:GET 请求的 Query String 或 POST 表单(application/x-www-form-urlencoded)中的单个键值对。
方式:使用 @RequestParam 或直接通过方法参数自动绑定(参数名与请求参数名一致)。
@RestController
@RequestMapping("/api/user")
public class UserController {
// 自动绑定:参数名必须一致
@GetMapping("/info")
public String getUserInfo(String name, Integer age) {
return "name: " + name + ", age: " + age;
}
// 显式指定 @RequestParam,可设置 required/defaultValue
@GetMapping("/detail")
public String getDetail(@RequestParam String name,
@RequestParam(required = false) Integer age,
@RequestParam(defaultValue = "1") int page) {
return String.format("name=%s, age=%s, page=%d", name, age, page);
}
}
- 注意:简单类型参数默认是
required=true(除非使用包装类并显式设置)。 - 推荐显式使用
@RequestParam提高可读性,并控制必填或默认值。
2. 实体参数(对象映射)
场景:多个参数具有业务关联性,希望封装成一个 POJO 对象。适用于 GET 查询参数或 POST 表单数据。
方式:直接将对象作为 Controller 方法参数,Spring 会自动将请求参数名与对象属性名匹配(调用 setter 或构造器绑定)。
// 实体类
@Data
public class UserQuery {
private String name;
private Integer age;
private String email;
}
// Controller
@GetMapping("/search")
public String searchUser(UserQuery query) {
return query.toString();
}
// 请求示例:GET /api/user/search?name=Tom&age=25&email=test@xx.com
- 支持嵌套对象、集合属性(需用
@RequestParam处理集合,见下文)。 - 若字段有校验需求,可结合
@Valid+@NotNull等注解,并在方法参数前加@Valid。
3. 数组与集合参数
场景:同一参数名出现多次(如 ids=1&ids=2&ids=3)或接收逗号分隔的字符串。
方式:使用数组或 List/Set 接收,配合 @RequestParam。
// 数组方式
@GetMapping("/batch")
public String batch(@RequestParam("ids") Integer[] ids) {
return Arrays.toString(ids);
}
// List 方式(推荐)
@GetMapping("/batch2")
public String batch2(@RequestParam("ids") List<Integer> ids) {
return ids.toString();
}
// 若使用逗号分隔:ids=1,2,3 也自动支持(需开启 StringToCollectionConverter)
- 若参数名与集合名一致,也可省略
@RequestParam,但建议显式声明以明确来源。 - 对于 POST 表单
application/x-www-form-urlencoded,同样适用。
4. 日期参数
场景:接收 Date、LocalDate、LocalDateTime 等时间类型。
方式:使用 @DateTimeFormat 注解指定格式化模式(Spring 会自动注册转换器)。
@GetMapping("/by-date")
public String getByDate(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime dateTime) {
return "date: " + date + ", datetime: " + dateTime;
}
// 请求示例:/by-date?date=2026-08-04&dateTime=2026-08-04 14:30:00
- 全局配置:可在
application.yml中配置 spring.mvc.format.date 等属性统一格式,或自定义Formatter。 - 注意时区问题,可使用
@DateTimeFormat的timezone属性或全局配置。
5. JSON 参数(请求体)
场景:POST/PUT 请求,Content-Type 为 application/json,传递复杂嵌套对象。
方式:使用 @RequestBody 将 JSON 映射到 Java 对象。
@Data
public class User {
private Long id;
private String name;
private Address address; // 嵌套对象
private List<String> tags;
}
@PostMapping("/create")
public String createUser(@RequestBody User user) {
// 自动反序列化
return "user: " + user.toString();
}
// 请求体示例:{"id":1,"name":"Alice","address":{"city":"Beijing"},"tags":["java","spring"]}
- 注意事项:
- 必须指定
@RequestBody,且确保 Jackson 依赖在 classpath 中(spring-boot-starter-web 已包含)。 - 支持校验:
@Valid User user配合校验注解。 - 如果希望使用
application/xml,需添加 Jackson XML 扩展,但一般常用 JSON。
6. 路径参数(@PathVariable)
场景:RESTful 风格 URL,如 /api/user/{id}。
方式:使用 @PathVariable 绑定 URL 模板中的变量。
@GetMapping("/{id}")
public String getUserById(@PathVariable("id") Long userId) {
return "user id: " + userId;
}
// 多个路径参数
@GetMapping("/{userId}/orders/{orderId}")
public String getOrder(@PathVariable Long userId, @PathVariable Long orderId) {
return userId + " - " + orderId;
}
- 若参数名与模板变量名一致,可省略
@PathVariable的 value。 - 路径参数类型支持基本类型、字符串等。
7. 其他常用注解补充
@RequestHeader:获取请求头。@CookieValue:获取 Cookie。@RequestAttribute:获取 request 域属性(常用于拦截器/过滤器传递)。@MatrixVariable:支持矩阵变量(较少用,需开启支持)。
二、统一响应结果
为了给前端(或调用方)提供一致的接口返回格式,我们通常封装一个通用响应类,包含状态码、消息、数据等字段,并结合全局异常处理,确保所有接口输出结构相同。
1. 通用响应类设计
import lombok.Data;
@Data
public class Result<T> {
private Integer code; // 业务状态码,非 HTTP 状态码
private String message; // 提示信息
private T data; // 数据载荷
// 私有构造,通过静态工厂方法创建
private Result() {}
// 成功(带数据)
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
// 成功(无数据)
public static <T> Result<T> success() {
return success(null);
}
// 失败(自定义状态码和信息)
public static <T> Result<T> error(Integer code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
// 失败(使用预定义错误枚举,见下文)
public static <T> Result<T> error(ErrorCode errorCode) {
return error(errorCode.getCode(), errorCode.getMessage());
}
}
常见设计思路:
- 使用泛型确保 data 字段类型安全。
- 业务状态码通常 200 表示成功,其他表示失败(如 400 参数错误,500 系统错误等),与 HTTP 状态码可一致也可不同,但建议区分。
- 有些团队使用
success布尔字段代替 code,但 code 更灵活,便于扩展错误码。
2. 错误码枚举
public enum ErrorCode {
SUCCESS(200, "success"),
BAD_REQUEST(400, "请求参数错误"),
UNAUTHORIZED(401, "未授权"),
FORBIDDEN(403, "禁止访问"),
NOT_FOUND(404, "资源不存在"),
INTERNAL_ERROR(500, "系统内部错误"),
// 自定义业务错误,例如 1001 起
USER_NOT_FOUND(1001, "用户不存在"),
INVALID_PARAM(1002, "参数校验失败");
private final int code;
private final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
// getter...
}
3. Controller 中使用统一响应
@RestController
@RequestMapping("/api/user")
public class UserController {
@GetMapping("/{id}")
public Result<User> getUser(@PathVariable Long id) {
User user = userService.getById(id);
if (user == null) {
return Result.error(ErrorCode.USER_NOT_FOUND);
}
return Result.success(user);
}
@PostMapping
public Result<Long> createUser(@Valid @RequestBody User user) {
Long id = userService.create(user);
return Result.success(id);
}
}
- 这样所有接口返回类型均为
Result<T>,前端可以统一处理。
4. 全局异常处理(统一错误响应)
使用 @ControllerAdvice + @ExceptionHandler 将未捕获的异常转换为标准 Result 格式,确保所有异常输出也符合规范。
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理参数校验异常(如 @Valid 失败)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleValidationException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining("; "));
return Result.error(ErrorCode.INVALID_PARAM.getCode(), message);
}
// 处理业务自定义异常(例如 UserNotFoundException)
@ExceptionHandler(BusinessException.class)
public Result<?> handleBusinessException(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}
// 处理其他未预料异常
@ExceptionHandler(Exception.class)
public Result<?> handleGlobalException(Exception e) {
// 记录日志
log.error("系统异常", e);
return Result.error(ErrorCode.INTERNAL_ERROR);
}
}
- 通过全局异常处理,Controller 层无需 try-catch,代码更简洁。
5. 统一响应进阶:HTTP 状态码与业务码分离
建议:
- HTTP 状态码遵循 RESTful 语义(如 200 OK, 400 Bad Request, 404 Not Found, 500 Internal Server Error)。
- 业务状态码独立于 HTTP 状态码,用于前端逻辑判断。
- 在异常处理中,可以设置
HttpServletResponse的 status,使 HTTP 状态码与实际错误匹配。
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleValidationException(HttpServletResponse response) {
response.setStatus(HttpStatus.BAD_REQUEST.value());
return Result.error(ErrorCode.INVALID_PARAM);
}
- 这样既保留了 RESTful 语义,又保留了业务错误码的细粒度。
6. 响应中的时间格式化
对于日期字段,默认 Jackson 序列化为时间戳(或 ISO 格式)。可以在实体字段上使用 @JsonFormat 指定格式,或全局配置:
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
serialization:
write-dates-as-timestamps: false # 禁用时间戳,使用格式化字符串
或使用 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 在字段上指定。
三、最佳实践总结
- 参数接收:
- GET 查询参数:简单类型/实体对象/集合,用
@RequestParam或自动绑定。 - 路径变量:用
@PathVariable。 - POST/PUT JSON:用
@RequestBody。 - 日期:用
@DateTimeFormat,并全局配置默认格式。 - 参数校验:结合
@Valid和校验注解,配合全局异常处理。
- 响应设计:
- 统一
Result<T>结构,包含code、message、data。 - 使用枚举管理业务错误码。
- 全局异常处理统一转化为
Result,避免 Controller 冗余 try-catch。 - 适当设置 HTTP 状态码,使接口更加 RESTful。
- 日志与监控:
- 在全局异常中记录错误日志,便于排查。
- 可在拦截器中记录请求参数和响应内容(但注意敏感信息脱敏)。
- 版本与兼容:
- 如需接口版本管理,可使用 URL 版本(/v1/api)或请求头,不影响参数接收。
Comments NOTHING