javaweb-Day05-Spring Boot与请求响应处理

TJCcc 发布于 20 天前 17 次阅读


完整示例代码结构

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. 日期参数

场景:接收 DateLocalDateLocalDateTime 等时间类型。

方式:使用 @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
  • 注意时区问题,可使用 @DateTimeFormattimezone 属性或全局配置。

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") 在字段上指定。


三、最佳实践总结

  1. 参数接收
  • GET 查询参数:简单类型/实体对象/集合,用 @RequestParam 或自动绑定。
  • 路径变量:用 @PathVariable
  • POST/PUT JSON:用 @RequestBody
  • 日期:用 @DateTimeFormat,并全局配置默认格式。
  • 参数校验:结合 @Valid 和校验注解,配合全局异常处理。
  1. 响应设计
  • 统一 Result<T> 结构,包含 codemessagedata
  • 使用枚举管理业务错误码。
  • 全局异常处理统一转化为 Result,避免 Controller 冗余 try-catch。
  • 适当设置 HTTP 状态码,使接口更加 RESTful。
  1. 日志与监控
  • 在全局异常中记录错误日志,便于排查。
  • 可在拦截器中记录请求参数和响应内容(但注意敏感信息脱敏)。
  1. 版本与兼容
  • 如需接口版本管理,可使用 URL 版本(/v1/api)或请求头,不影响参数接收。
唯有极致沉淀,才能造就辉煌。
最后更新于 2026-08-04