响应
返回 JSON(最常用场景)
自动序列化
java
@RestController
public class UserController {
@GetMapping("/users")
public List<User> index() {
return userService.findAll(); // 自动转 JSON 数组
}
@GetMapping("/users/{id}")
public User show(@PathVariable Long id) {
return userService.findById(id); // 自动转 JSON 对象
}
}只要类上有
@RestController,方法的返回值就会被 Jackson 库自动序列化成 JSON。 原理:Spring 检测到@ResponseBody(@RestController自带),就调用ObjectMapper把 Java 对象转成 JSON。
ResponseEntity:精确控制
java
@PostMapping("/users")
public ResponseEntity<User> create(@RequestBody User user) {
User saved = userService.save(user);
return ResponseEntity
.status(HttpStatus.CREATED) // 201
.header("X-Resource-Id", saved.getId().toString())
.body(saved);
}
@DeleteMapping("/users/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build(); // 204
}
@GetMapping("/users/{id}")
public ResponseEntity<User> show(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok) // 200
.orElse(ResponseEntity.notFound().build()); // 404
}统一响应格式
PHP 项目中常见的统一响应格式:
php
// Laravel 中可能在 AppServiceProvider 或 BaseController 中定义
return response()->json([
'code' => 0,
'message' => 'success',
'data' => $data
]);Spring Boot 中实现方式:
定义通用响应类
java
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> r = new ApiResponse<>();
r.code = 0;
r.message = "success";
r.data = data;
return r;
}
public static <T> ApiResponse<T> error(int code, String message) {
ApiResponse<T> r = new ApiResponse<>();
r.code = code;
r.message = message;
return r;
}
// getter / setter
}全局统一包装
java
// 方式一:控制器统一返回 ApiResponse
@GetMapping("/users")
public ApiResponse<List<User>> index() {
return ApiResponse.success(userService.findAll());
}
// 方式二:使用 ResponseBodyAdvice 全局拦截(类似中间件处理响应)
@ControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// 排除已经包装过的和某些特殊类型
return !returnType.getParameterType().equals(ApiResponse.class);
}
@Override
public Object beforeBodyWrite(Object body, ...) {
return ApiResponse.success(body);
}
}⚠️ 注意:方式二是全局 AOP,会影响到所有返回值,404 等异常情况需要额外处理。建议初期直接用方式一,手动包装返回值。
异常时返回错误 JSON
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ApiResponse<Void> handleNotFound(ResourceNotFoundException e) {
return ApiResponse.error(1001, e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Map<String, String>> handleValidation(
MethodArgumentNotValidException e) {
Map<String, String> errors = new HashMap<>();
e.getBindingResult().getFieldErrors()
.forEach(err -> errors.put(err.getField(), err.getDefaultMessage()));
return ApiResponse.error(1002, "Validation failed")
.data(errors); // 伪代码,需要自己扩展 ApiResponse
}
}⚠️ 注意事项
1. null 会被序列化为 null,不是字段缺失
java
public class User {
private String name;
private String email;
private String phone; // 可能为 null
}
// 结果: {"name":"foo", "email":"foo@bar.com", "phone":null}需要忽略 null 字段:
yaml# application.yml spring: jackson: default-property-inclusion: non_null
2. 循环引用会导致栈溢出
java
public class User {
private List<Post> posts; // User → Post
}
public class Post {
private User author; // Post → User(循环)
}
// 序列化时:JsonMappingException: Infinite recursion解决:在双向关联的一侧加
@JsonIgnore或@JsonBackReference。
3. 没有 dd() / dump()
java
// PHP:dd($variable) 打印并终止
// Java 中:
System.out.println(variable); // 打印到控制台,不终止
log.debug("variable: {}", variable); // 推荐,日志方式Java 中没有
dd()。调试推荐用 IDE 断点调试,比dd()高效得多。