响应
Laravel 里
return response()->json($data)或return $data;Spring Boot 的@RestController让「返回对象 = 自动 JSON」,再用ResponseEntity精细控制状态码和响应头。
最基本的响应:返回对象即 JSON
java
@GetMapping("/posts/{id}")
public Post show(@PathVariable Long id) {
return service.get(id);
}@RestController 下,返回值会被 HttpMessageConverter 自动序列化为 JSON 并返回 200。Post 的 getter 有哪些字段,JSON 里就有哪些字段。
TIP
@RestController = @Controller + 方法上的 @ResponseBody。用了它,方法返回值就不是「视图名」,而是「响应体」。
控制状态码和响应头:ResponseEntity
Laravel 用 response()->json($data, 201);Spring 用 ResponseEntity:
java
@PostMapping("/posts")
public ResponseEntity<Post> store(@Valid @RequestBody PostDto dto) {
Post post = service.create(dto);
return ResponseEntity
.status(HttpStatus.CREATED) // 201
.header("X-Post-Id", post.getId().toString())
.body(post);
}常用构建器:
java
return ResponseEntity.ok(post); // 200
return ResponseEntity.status(201).body(post); // 201 + body
return ResponseEntity.noContent().build(); // 204,无 body
return ResponseEntity.badRequest().build(); // 400
return ResponseEntity.notFound().build(); // 404
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.header("X-Custom", "v1")
.body(post);推荐写法
- 成功且有数据:返回对象本身(
Post),或ResponseEntity.ok(post)自定义响应头 - 创建成功:
ResponseEntity.status(201).body(...) - 无内容:
ResponseEntity.noContent().build() - 出错:不要在控制器里 return 400/500,应该
throw异常交给全局处理器(见 异常处理)
设置 HTTP 状态码的三种方式
| 方式 | 写法 | 场景 |
|---|---|---|
返回对象 + @ResponseStatus | @ResponseStatus(HttpStatus.CREATED) | 固定状态码 |
ResponseEntity | ResponseEntity.status(201) | 动态状态码 + 自定义头 |
抛异常 + @ResponseStatus | 自定义异常上标注 | 业务异常 |
java
@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/posts")
public Post store(@Valid @RequestBody PostDto dto) {
return service.create(dto);
}JSON 输出定制
字段改名
java
@Data
public class Post {
@JsonProperty("post_id")
private Long id;
}输出:{ "post_id": 1 }。
忽略字段
java
@Data
public class User {
private Long id;
@JsonIgnore
private String passwordHash; // 响应里不输出
}日期格式
yaml
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/ShanghaiTIP
给实体直接加 @JsonIgnore 会同时影响入参序列化,容易踩坑。更干净的做法是用 DTO 分别定义入参/出参结构,见 JPA 序列化与 DTO。
统一响应包装:和 Laravel 一样的套路
很多团队喜欢统一的 {code, message, data} 结构,就像 Laravel 里自定义 ApiResponse:
java
@Data
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> ok(T data) {
return of(0, "ok", data);
}
public static <T> ApiResponse<T> of(int code, String message, T data) {
ApiResponse<T> r = new ApiResponse<>();
r.code = code;
r.message = message;
r.data = data;
return r;
}
}控制器统一返回:
java
@GetMapping("/posts")
public ApiResponse<List<Post>> index() {
return ApiResponse.ok(service.list());
}两种风格二选一
- 裸数据:直接返回
Post,状态码表达语义(REST 风格,前后端约定好) - 包装数据:统一
{code,message,data},状态码永远是 200 团队里必须统一。本教程示例两种都会出现,实际项目选一种定下来。
返回文件下载
java
@GetMapping("/download/{fileId}")
public ResponseEntity<Resource> download(@PathVariable Long fileId) {
Resource resource = fileStorage.load(fileId);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + resource.getFilename() + "\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
}重定向
java
@GetMapping("/old-url")
public ResponseEntity<Void> redirect() {
return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY)
.location(URI.create("/posts"))
.build();
}响应对照速查
| 需求 | Laravel | Spring Boot |
|---|---|---|
| JSON 数据 | return $posts; | return posts; |
| 自定义状态码 | response()->json($d, 201) | ResponseEntity.status(201).body(d) |
| 无内容 | response()->noContent() | ResponseEntity.noContent().build() |
| 定制响应头 | response()->header('X-Foo','1') | ResponseEntity.ok().header("X-Foo","1").body(d) |
| 输出字段改名 | $resource->toArray() | @JsonProperty("xxx") |
| 隐藏字段 | $hidden | @JsonIgnore |
| 下载 | response()->download($path) | 返回 ResponseEntity<Resource> |
接下来:请求与响应之间,如何做横切逻辑 —— 中间件(Interceptor / Filter)。