前言
在 Spring MVC 或 Spring Boot 开发 REST API 时,我们最常见的写法可能是直接从 Controller 返回一个 Java 对象:
1 2 3 4
| @GetMapping("/users/{id}") public User getUser(@PathVariable Long id) { return userService.findById(id); }
|
Spring 会通过 HttpMessageConverter 将对象序列化成 JSON,并默认返回 200 OK。
这种写法足够简单,但真实项目中的 HTTP 响应通常不只有响应体,还包括:
- HTTP 状态码;
- Response Header;
- Response Body;
- 缓存控制;
- Location;
- Content-Type;
- ETag 等协议级信息。
当接口需要对这些内容进行精细控制时,ResponseEntity<T> 就非常合适。
ResponseEntity 的核心思想可以概括成一句话:
它不是单纯的数据包装类,而是 Spring 对一个完整 HTTP Response 的抽象。
本文结合实际开发场景,系统整理 ResponseEntity 的基本用法、RESTful API 设计方式、统一返回对象、异常处理、文件下载以及常见误区。
ResponseEntity 是什么
ResponseEntity<T> 位于:
1
| org.springframework.http.ResponseEntity
|
它继承自 HttpEntity<T>:
1 2 3 4 5 6 7 8
| HttpEntity<T> ├── headers └── body
ResponseEntity<T> ├── headers ├── body └── statusCode
|
HttpEntity 负责描述 HTTP Header 和 Body,而 ResponseEntity 在此基础上增加了 HTTP Status Code。
因此,一个 ResponseEntity 可以完整表达:
1 2 3 4
| HTTP Response ├── Status Code ├── Headers └── Body
|
例如:
1 2 3 4 5 6 7 8
| HTTP/1.1 200 OK Content-Type: application/json X-Request-Id: 7e91c4
{ "id": 1001, "name": "Mario" }
|
在 Java 中可以表示为:
其中泛型 User 表示响应体的数据类型。
ResponseEntity 的基本语法
最直接的构造方式是:
1 2
| ResponseEntity<T> response = new ResponseEntity<>(body, headers, status);
|
例如:
1 2 3 4 5 6 7 8 9 10 11
| @GetMapping("/hello") public ResponseEntity<String> hello() { HttpHeaders headers = new HttpHeaders(); headers.add("X-App", "demo");
return new ResponseEntity<>( "Hello Spring", headers, HttpStatus.OK ); }
|
对应 HTTP 响应大致为:
1 2 3 4 5
| HTTP/1.1 200 OK X-App: demo Content-Type: text/plain
Hello Spring
|
不过在实际开发中,更推荐使用 ResponseEntity 提供的 Builder API,因为可读性更好。
最常用的 Builder 写法
返回 200 OK
最常见的方式:
1 2 3 4 5
| @GetMapping("/users/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { User user = userService.findById(id); return ResponseEntity.ok(user); }
|
等价于:
1
| return new ResponseEntity<>(user, HttpStatus.OK);
|
如果只需要返回状态码:
1
| return ResponseEntity.ok().build();
|
返回指定 HTTP 状态码
可以通过 status() 设置任意 HTTP 状态:
1 2 3
| return ResponseEntity .status(HttpStatus.NOT_FOUND) .body("User not found");
|
也可以直接使用数字:
1 2 3
| return ResponseEntity .status(404) .body("User not found");
|
通常还是建议优先使用 HttpStatus,代码语义更加清晰。
常见快捷方法
ResponseEntity 提供了大量常见 HTTP 状态的快捷 Builder。
200 OK
1
| return ResponseEntity.ok(data);
|
201 Created
1
| return ResponseEntity.created(location).body(data);
|
202 Accepted
1
| return ResponseEntity.accepted().build();
|
204 No Content
1
| return ResponseEntity.noContent().build();
|
400 Bad Request
1
| return ResponseEntity.badRequest().body(error);
|
404 Not Found
1
| return ResponseEntity.notFound().build();
|
这些方法比:
1
| new ResponseEntity<>(..., HttpStatus.xxx)
|
通常更容易表达接口意图。
用 ResponseEntity 设计 CRUD API
ResponseEntity 最有价值的地方之一,就是让 Controller 能够更加准确地表达 HTTP 语义。
假设存在一个用户接口:
1 2 3 4
| POST /api/users GET /api/users/{id} PUT /api/users/{id} DELETE /api/users/{id}
|
可以按照下面的方式设计。
查询资源:200 或 404
1 2 3 4 5 6
| @GetMapping("/api/users/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { return userService.findById(id) .map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.notFound().build()); }
|
存在资源:
不存在资源:
如果 Service 返回的是 Optional<User>,还可以进一步简化:
1 2 3 4
| @GetMapping("/api/users/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { return ResponseEntity.of(userService.findById(id)); }
|
这种写法非常适合:
1 2 3 4 5
| Optional.empty() -> 404 Not Found
Optional<User> -> 200 OK + User
|
创建资源:201 Created
对于创建资源的 POST 请求,更标准的返回状态通常是:
并通过 Location Header 告诉客户端新资源的位置。
例如:
1 2 3 4 5 6 7 8 9 10 11
| @PostMapping("/api/users") public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) {
User user = userService.create(request);
URI location = URI.create("/api/users/" + user.getId());
return ResponseEntity .created(location) .body(user); }
|
返回:
1 2 3
| HTTP/1.1 201 Created Location: /api/users/1001 Content-Type: application/json
|
Body:
1 2 3 4
| { "id": 1001, "name": "Mario" }
|
ResponseEntity.created(location) 会同时表达两件事:
1 2
| Status = 201 Created Location = /api/users/1001
|
比单独写:
更符合资源创建的 HTTP 语义。
删除资源:204 No Content
删除成功后如果不需要返回数据,可以使用:
1 2 3 4 5 6 7 8 9 10 11
| @DeleteMapping("/api/users/{id}") public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
boolean deleted = userService.delete(id);
if (!deleted) { return ResponseEntity.notFound().build(); }
return ResponseEntity.noContent().build(); }
|
删除成功:
资源不存在:
需要注意:
204 No Content 的语义就是没有响应体,因此不要一边返回 204,一边又向 Body 中塞数据。
更新资源
更新成功后可以根据接口设计返回:
或者:
例如返回更新后的对象:
1 2 3 4 5 6 7 8 9
| @PutMapping("/api/users/{id}") public ResponseEntity<User> updateUser( @PathVariable Long id, @RequestBody UpdateUserRequest request) {
User user = userService.update(id, request);
return ResponseEntity.ok(user); }
|
如果客户端不需要更新后的资源:
1
| return ResponseEntity.noContent().build();
|
除了状态码之外,ResponseEntity 的另一大用途是操作响应头。
例如:
1 2 3 4 5 6 7 8
| @GetMapping("/api/header") public ResponseEntity<String> customHeader() { return ResponseEntity .ok() .header("X-Service-Version", "v1") .header("X-Trace-Id", "7e91c4") .body("success"); }
|
返回:
1 2 3 4 5
| HTTP/1.1 200 OK X-Service-Version: v1 X-Trace-Id: 7e91c4
success
|
也可以使用 HttpHeaders:
1 2 3 4 5
| HttpHeaders headers = new HttpHeaders(); headers.set("X-Trace-Id", traceId); headers.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(data, headers, HttpStatus.OK);
|
设置 Content-Type
例如明确返回 JSON:
1 2 3 4
| return ResponseEntity .ok() .contentType(MediaType.APPLICATION_JSON) .body(data);
|
如果返回文件:
1 2 3 4
| return ResponseEntity .ok() .contentType(MediaType.APPLICATION_PDF) .body(resource);
|
一般情况下 Spring 会根据返回对象和 HttpMessageConverter 自动确定 Content-Type,只有接口存在特殊需求时才需要显式设置。
ResponseEntity 与泛型
ResponseEntity 本身是一个泛型类:
因此 Body 可以是任意类型。
字符串:
对象:
集合:
1
| ResponseEntity<List<User>>
|
分页对象:
1
| ResponseEntity<Page<User>>
|
统一响应对象:
1
| ResponseEntity<ApiResponse<User>>
|
文件:
1
| ResponseEntity<Resource>
|
没有 Body:
泛型的意义是让接口返回值在编译期就具备明确类型,而不是大量使用:
不建议滥用 ResponseEntity
很多项目为了“方便”,会写成:
1
| public ResponseEntity<Object> getUser(...)
|
然后不同情况下返回:
1 2 3 4 5
| User String Map List Error
|
虽然代码可以运行,但是接口契约会变得模糊。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @GetMapping("/users/{id}") public ResponseEntity<Object> getUser(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) { return ResponseEntity .status(HttpStatus.NOT_FOUND) .body("User not found"); }
return ResponseEntity.ok(user); }
|
此时客户端无法仅通过接口签名判断 Body 到底是什么结构。
更推荐定义稳定的错误模型,或者使用统一响应对象。
ResponseEntity 与统一响应对象并不冲突
很多企业项目都会定义统一的 API 返回结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public record ApiResponse<T>( int code, String message, T data ) {
public static <T> ApiResponse<T> success(T data) { return new ApiResponse<>(0, "success", data); }
public static <T> ApiResponse<T> error(int code, String message) { return new ApiResponse<>(code, message, null); } }
|
Controller:
1 2 3 4 5 6 7 8 9
| @GetMapping("/api/users/{id}") public ResponseEntity<ApiResponse<User>> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return ResponseEntity.ok( ApiResponse.success(user) ); }
|
这里其实存在两个不同层次:
1 2 3 4 5 6 7 8 9 10 11
| HTTP 层 ResponseEntity ├── HTTP Status ├── HTTP Headers └── Body
业务层 ApiResponse ├── code ├── message └── data
|
因此二者并不是二选一。
可以理解为:
1
| ResponseEntity<ApiResponse<User>>
|
其中:
1 2 3 4 5
| ResponseEntity -> 描述 HTTP 协议语义
ApiResponse -> 描述业务响应结构
|
不要让所有接口永远返回 HTTP 200
很多传统项目会这样设计:
然后:
1 2 3 4 5
| { "code": 500, "message": "system error", "data": null }
|
或者:
1 2 3 4 5
| { "code": 404, "message": "user not found", "data": null }
|
这种设计并不是完全不能使用,但会削弱 HTTP 本身已经提供的状态语义。
例如监控系统、API Gateway、浏览器、负载均衡器、客户端 SDK,通常首先能直接看到 HTTP Status。
更清晰的设计是:
Body:
1 2 3 4
| { "code": "USER_NOT_FOUND", "message": "User not found" }
|
也就是说:
1 2 3 4 5
| HTTP Status -> 描述请求在 HTTP 层面的结果
Business Code -> 描述业务系统内部的错误类型
|
两者各司其职。
使用 ResponseEntity 处理参数校验错误
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @PostMapping("/api/users") public ResponseEntity<Map<String, String>> createUser( @Valid @RequestBody CreateUserRequest request, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
Map<String, String> errors = new HashMap<>();
bindingResult.getFieldErrors().forEach(error -> errors.put( error.getField(), error.getDefaultMessage() ) );
return ResponseEntity .badRequest() .body(errors); }
userService.create(request);
return ResponseEntity .status(HttpStatus.CREATED) .build(); }
|
校验失败:
1
| HTTP/1.1 400 Bad Request
|
Body:
1 2 3 4
| { "username": "用户名不能为空", "email": "邮箱格式错误" }
|
不过在大型项目中,不建议每一个 Controller 都手动写 BindingResult。
更好的方式是将参数校验异常交给全局异常处理器。
ResponseEntity + @RestControllerAdvice
真实项目中,Controller 最好专注业务流程,而不是到处复制错误处理代码。
可以使用:
配合:
统一构造 ResponseEntity。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @RestControllerAdvice public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class) public ResponseEntity<ApiError> handleUserNotFound( UserNotFoundException ex) {
ApiError error = new ApiError( "USER_NOT_FOUND", ex.getMessage() );
return ResponseEntity .status(HttpStatus.NOT_FOUND) .body(error); }
@ExceptionHandler(Exception.class) public ResponseEntity<ApiError> handleException(Exception ex) {
ApiError error = new ApiError( "INTERNAL_ERROR", "Internal server error" );
return ResponseEntity .status(HttpStatus.INTERNAL_SERVER_ERROR) .body(error); } }
|
错误对象:
1 2 3 4 5
| public record ApiError( String code, String message ) { }
|
这样 Controller 就可以保持简单:
1 2 3 4 5 6
| @GetMapping("/api/users/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { return ResponseEntity.ok( userService.getById(id) ); }
|
如果用户不存在:
1
| throw new UserNotFoundException("User not found");
|
由异常处理层统一转换成:
1 2
| HTTP/1.1 404 Not Found Content-Type: application/json
|
1 2 3 4
| { "code": "USER_NOT_FOUND", "message": "User not found" }
|
Spring Boot 3 中可以结合 ProblemDetail
Spring Boot 3 基于 Spring Framework 6,可以使用 ProblemDetail 构造标准化 HTTP 错误响应。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @ExceptionHandler(UserNotFoundException.class) public ResponseEntity<ProblemDetail> handleUserNotFound( UserNotFoundException ex) {
ProblemDetail problem = ProblemDetail.forStatus(HttpStatus.NOT_FOUND);
problem.setTitle("User Not Found"); problem.setDetail(ex.getMessage());
return ResponseEntity .status(HttpStatus.NOT_FOUND) .body(problem); }
|
对于面向外部开放的 REST API,ProblemDetail 可以减少自定义错误模型的重复设计。
如果是企业内部已有统一错误协议,也完全可以继续使用自己的 ApiError。
分页接口
ResponseEntity 可以直接包装 Spring Data 的 Page<T>:
1 2 3 4 5 6 7 8 9 10 11
| @GetMapping("/api/users") public ResponseEntity<Page<User>> listUsers( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) {
Pageable pageable = PageRequest.of(page, size);
Page<User> result = userService.findAll(pageable);
return ResponseEntity.ok(result); }
|
不过对于公开 API,通常更推荐定义自己的分页 DTO,避免把框架内部的数据结构直接暴露给客户端。
例如:
1 2 3 4 5 6 7
| public record PageResponse<T>( List<T> records, long total, int page, int size ) { }
|
然后:
1 2 3 4 5 6 7 8
| return ResponseEntity.ok( new PageResponse<>( result.getContent(), result.getTotalElements(), result.getNumber(), result.getSize() ) );
|
使用 ResponseEntity 下载文件
ResponseEntity 也非常适合文件下载,因为文件接口通常需要同时控制:
1 2 3 4 5
| Content-Type Content-Disposition Content-Length Body Status
|
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @GetMapping("/api/files/{name}") public ResponseEntity<Resource> download( @PathVariable String name) {
Resource resource = fileService.load(name);
return ResponseEntity .ok() .contentType(MediaType.APPLICATION_OCTET_STREAM) .header( HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + name + "\"" ) .body(resource); }
|
浏览器收到:
1 2 3
| HTTP/1.1 200 OK Content-Type: application/octet-stream Content-Disposition: attachment; filename="demo.pdf"
|
Response Body 则是文件内容。
对于文件、流、缓存资源等接口,ResponseEntity 往往比单纯返回对象更自然。
缓存控制
Response Header 并不仅仅是放自定义字段,还可以控制 HTTP Cache。
例如:
1 2 3 4 5 6
| return ResponseEntity .ok() .cacheControl( CacheControl.maxAge(Duration.ofMinutes(10)) ) .body(data);
|
还可以设置 ETag:
1 2 3 4
| return ResponseEntity .ok() .eTag("\"user-1001-v3\"") .body(user);
|
返回:
因此 ResponseEntity 不只是“改状态码”的工具,它可以参与完整的 HTTP 协议设计。
ResponseEntity 与 @ResponseBody 的区别
普通的:
1 2 3 4 5
| @ResponseBody @GetMapping("/hello") public User hello() { return user; }
|
主要表达的是:
1
| 把返回值写入 HTTP Response Body
|
而:
1 2 3 4
| @GetMapping("/hello") public ResponseEntity<User> hello() { return ResponseEntity.ok(user); }
|
表达的是:
1 2 3 4
| HTTP Response ├── Status ├── Header └── Body
|
可以简单理解为:
1 2 3 4 5
| @ResponseBody -> 我主要关心 Body
ResponseEntity -> 我需要控制完整 HTTP Response
|
在 @RestController 中已经隐含了 @ResponseBody,因此使用 ResponseEntity 时通常不需要额外添加 @ResponseBody。
ResponseEntity 与 @ResponseStatus 的区别
Spring 还提供:
例如:
1 2 3 4 5
| @ResponseStatus(HttpStatus.CREATED) @PostMapping("/api/users") public User createUser(...) { return userService.create(...); }
|
这种方式适合状态码固定的场景。
但如果一个接口根据业务结果动态返回:
那么 ResponseEntity 更灵活。
例如:
1 2 3 4 5
| if (user == null) { return ResponseEntity.notFound().build(); }
return ResponseEntity.ok(user);
|
因此:
1 2 3 4 5
| @ResponseStatus -> 静态状态码
ResponseEntity -> 动态构造整个 HTTP Response
|
ResponseEntity 与 HttpServletResponse 的区别
也可以直接操作 Servlet API:
1 2 3 4
| public void handle(HttpServletResponse response) { response.setStatus(200); response.setHeader("X-Test", "1"); }
|
但这种方式会让 Controller 与 Servlet API 强耦合。
而:
1 2 3 4
| return ResponseEntity .ok() .header("X-Test", "1") .body(data);
|
更加声明式,也更容易测试。
因此在普通 REST Controller 中,如果只是控制:
通常优先考虑 ResponseEntity。
是否所有 Controller 都应该返回 ResponseEntity
答案是:不需要。
例如:
1 2 3 4
| @GetMapping("/api/config") public Config getConfig() { return configService.get(); }
|
如果接口永远都是:
那么直接返回对象反而更加简单。
没有必要为了“统一”写成:
1 2 3 4
| @GetMapping("/api/config") public ResponseEntity<Config> getConfig() { return ResponseEntity.ok(configService.get()); }
|
虽然没有错,但价值有限。
更适合使用 ResponseEntity 的场景包括:
- 根据业务结果动态返回不同 HTTP Status;
- 需要设置响应头;
- RESTful CRUD 接口;
- 文件下载;
- Location;
- Cache-Control;
- ETag;
- 重定向;
- 全局异常处理;
- 需要精确表达 HTTP 协议语义的接口。
因此可以遵循一个原则:
如果只需要返回 Body,就直接返回对象;如果需要控制 HTTP Response,就使用 ResponseEntity。
常见 HTTP 状态码
REST API 中常见状态码如下。
| 状态码 |
HttpStatus |
常见场景 |
| 200 |
OK |
请求成功 |
| 201 |
CREATED |
创建资源成功 |
| 202 |
ACCEPTED |
请求已接受,异步处理中 |
| 204 |
NO_CONTENT |
请求成功但无需返回 Body |
| 400 |
BAD_REQUEST |
参数错误 |
| 401 |
UNAUTHORIZED |
未认证 |
| 403 |
FORBIDDEN |
已认证但无权限 |
| 404 |
NOT_FOUND |
资源不存在 |
| 409 |
CONFLICT |
资源状态冲突 |
| 422 |
UNPROCESSABLE_ENTITY |
请求格式正确但业务语义无法处理 |
| 429 |
TOO_MANY_REQUESTS |
请求过多 |
| 500 |
INTERNAL_SERVER_ERROR |
服务端异常 |
| 503 |
SERVICE_UNAVAILABLE |
服务暂时不可用 |
一个更完整的 Controller 示例
下面把前面的内容组合起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| @RestController @RequestMapping("/api/users") @RequiredArgsConstructor public class UserController {
private final UserService userService;
@GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { return ResponseEntity.of( userService.findById(id) ); }
@PostMapping public ResponseEntity<User> createUser( @Valid @RequestBody CreateUserRequest request) {
User user = userService.create(request);
URI location = URI.create( "/api/users/" + user.getId() );
return ResponseEntity .created(location) .body(user); }
@PutMapping("/{id}") public ResponseEntity<User> updateUser( @PathVariable Long id, @Valid @RequestBody UpdateUserRequest request) {
User user = userService.update(id, request);
return ResponseEntity.ok(user); }
@DeleteMapping("/{id}") public ResponseEntity<Void> deleteUser( @PathVariable Long id) {
userService.delete(id);
return ResponseEntity .noContent() .build(); } }
|
整个接口的语义非常明确:
1 2 3 4 5 6 7 8 9 10 11 12
| GET 成功 -> 200 不存在 -> 404
POST 成功 -> 201 + Location
PUT 成功 -> 200
DELETE 成功 -> 204
|
常见误区
误区一:ResponseEntity 只是统一返回对象
不是。
下面这种:
1 2 3 4 5
| public class Result<T> { private int code; private String message; private T data; }
|
属于业务响应模型。
而:
属于 HTTP Response 模型。
两者解决的问题不同。
误区二:用了 ResponseEntity 就必须再套 Result
不一定。
对于内部 API:
1
| ResponseEntity<ApiResponse<User>>
|
当然可以。
但对于标准 REST API:
同样完全合理。
错误响应可以由统一异常处理器转换成固定的错误格式。
误区三:接口错误也全部返回 200
例如:
1 2 3 4
| { "code": 404, "message": "not found" }
|
会让 HTTP 层失去很多原本已经定义好的协议语义。
更推荐:
1 2 3 4
| { "code": "USER_NOT_FOUND", "message": "User not found" }
|
误区四:204 还返回 Body
错误示例:
1 2 3
| return ResponseEntity .status(HttpStatus.NO_CONTENT) .body(data);
|
如果确实需要返回数据,应考虑使用:
如果使用:
就应该:
1
| return ResponseEntity.noContent().build();
|
误区五:所有接口都机械套 ResponseEntity
这种代码:
1
| return ResponseEntity.ok(service.query());
|
本身没有问题。
但如果接口根本不需要控制状态码和 Header:
可能更加简洁。
技术抽象应该解决实际问题,而不是增加仪式感。
WebFlux 中的 ResponseEntity
在 Spring WebFlux 中,同样可以使用 ResponseEntity。
例如:
1 2 3 4 5 6 7 8 9 10
| @GetMapping("/users/{id}") public Mono<ResponseEntity<User>> getUser( @PathVariable Long id) {
return userService.findById(id) .map(ResponseEntity::ok) .defaultIfEmpty( ResponseEntity.notFound().build() ); }
|
这里:
表示 HTTP 状态、Header 和 Body 都可以根据异步执行结果决定。
另一种形式:
则表示状态码和 Header 可以立即确定,而 Body 异步产生。
在普通 Spring MVC 项目中不用刻意关注这种区别,但在 WebFlux 项目中需要理解两者的时机差异。
最佳实践总结
实际项目中可以遵循下面几个原则。
1. 让 HTTP Status 表达 HTTP 结果
不要所有接口永远都是:
应该合理使用:
1 2 3 4 5 6 7 8 9
| 201 204 400 401 403 404 409 429 500
|
2. 让业务 Code 表达业务错误
例如:
1 2 3 4
| { "code": "ACCOUNT_DISABLED", "message": "Account has been disabled" }
|
同时 HTTP Status:
HTTP 协议和业务协议不要互相替代。
3. Controller 不要堆满异常判断
尽量把异常转换集中在:
中统一处理。
4. 创建资源时使用 201 + Location
推荐:
1 2 3
| return ResponseEntity .created(location) .body(resource);
|
5. 无 Body 时使用 ResponseEntity
例如:
比:
更能表达接口意图。
6. 尽量使用 Builder API
优先:
1 2 3 4 5 6 7 8 9 10 11 12 13
| ResponseEntity.ok(data);
ResponseEntity .status(HttpStatus.NOT_FOUND) .body(error);
ResponseEntity .created(location) .body(data);
ResponseEntity .noContent() .build();
|
而不是在业务代码中到处堆:
1
| new ResponseEntity<>(...)
|
Builder API 通常更加直观。
总结
ResponseEntity 是 Spring Web 开发中非常基础但又非常容易被低估的一个类。
它的核心价值并不是:
而是:
1
| 用 Java 对象完整表达一个 HTTP Response
|
也就是:
1 2 3 4
| ResponseEntity<T> ├── HTTP Status ├── HTTP Headers └── Response Body
|
在普通接口中,如果只需要:
直接返回 Java 对象已经足够。
但当接口涉及:
1 2 3 4 5 6 7 8
| RESTful 状态码 错误响应 Location 自定义 Header 文件下载 缓存 ETag 全局异常处理
|
ResponseEntity 就能够明显提高接口语义的清晰度。
真正值得掌握的不是:
这个方法本身,而是理解:
HTTP Status、Header 和 Body 本来就是一个完整协议响应的三个组成部分。
一旦从这个角度理解 ResponseEntity,很多 Spring REST API 的设计方式就会变得非常自然。
参考资料