Appearance
ResponseEntity:Spring MVC 中的完整 HTTP 响应控制器 🎯
什么是 ResponseEntity?
ResponseEntity
是 Spring MVC 中用于构建完整 HTTP 响应的强大工具。如果说 @ResponseBody
只能控制响应体,那么 ResponseEntity
就是一个全能选手——它不仅能控制响应体,还能精确控制 HTTP 状态码和响应头。
TIP
把 ResponseEntity
想象成一个完整的 HTTP 响应包装器,它让你能够像搭积木一样构建响应:状态码 + 响应头 + 响应体 = 完整的 HTTP 响应
为什么需要 ResponseEntity?
在 Web 开发中,我们经常遇到这些场景:
- 需要返回特定的 HTTP 状态码(如 201 Created、404 Not Found)
- 需要设置自定义响应头(如缓存控制、ETag)
- 需要根据业务逻辑动态决定响应内容
- 需要处理文件下载等特殊响应
传统的 @ResponseBody
只能处理响应体,无法满足这些复杂需求。
kotlin
@GetMapping("/user/{id}")
@ResponseBody
fun getUser(@PathVariable id: Long): User? {
val user = userService.findById(id)
// 问题:如果用户不存在,只能返回 null
// 但无法设置 404 状态码!
return user
}
kotlin
@GetMapping("/user/{id}")
fun getUser(@PathVariable id: Long): ResponseEntity<User> {
val user = userService.findById(id)
return if (user != null) {
ResponseEntity.ok(user) // 200 OK + 用户数据
} else {
ResponseEntity.notFound().build() // 404 Not Found
}
}
ResponseEntity 的核心特性
1. 状态码控制
kotlin
@RestController
class UserController {
@PostMapping("/users")
fun createUser(@RequestBody user: User): ResponseEntity<User> {
val savedUser = userService.save(user)
return ResponseEntity
.status(HttpStatus.CREATED)
.body(savedUser)
}
@DeleteMapping("/users/{id}")
fun deleteUser(@PathVariable id: Long): ResponseEntity<Void> {
return if (userService.existsById(id)) {
userService.deleteById(id)
ResponseEntity.noContent().build() // 204 No Content
} else {
ResponseEntity.notFound().build() // 404 Not Found
}
}
}
2. 响应头控制
kotlin
@GetMapping("/users/{id}")
fun getUserWithHeaders(@PathVariable id: Long): ResponseEntity<User> {
val user = userService.findById(id) ?: return ResponseEntity.notFound().build()
// 计算 ETag 用于缓存控制
val etag = "\"${user.hashCode()}\""
return ResponseEntity.ok()
.eTag(etag)
.cacheControl(CacheControl.maxAge(Duration.ofMinutes(10)))
.header("X-Custom-Header", "MyValue")
.body(user)
}
3. 条件响应
kotlin
@GetMapping("/users/{id}")
fun getUserConditional(
@PathVariable id: Long,
request: HttpServletRequest
): ResponseEntity<User> {
val user = userService.findById(id) ?: return ResponseEntity.notFound().build()
val etag = "\"${user.hashCode()}\""
// 检查客户端缓存
val ifNoneMatch = request.getHeader("If-None-Match")
if (etag == ifNoneMatch) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build()
}
return ResponseEntity.ok()
.eTag(etag)
.body(user)
}
实际业务场景应用
场景1:API 响应标准化
kotlin
@RestController
class ApiController {
// 统一的 API 响应格式
data class ApiResponse<T>(
val success: Boolean,
val message: String,
val data: T? = null,
val timestamp: Long = System.currentTimeMillis()
)
@GetMapping("/api/products/{id}")
fun getProduct(@PathVariable id: Long): ResponseEntity<ApiResponse<Product>> {
return try {
val product = productService.findById(id)
if (product != null) {
ResponseEntity.ok(
ApiResponse(
success = true,
message = "Product found",
data = product
)
)
} else {
ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(
ApiResponse<Product>(
success = false,
message = "Product not found"
)
)
}
} catch (e: Exception) {
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(
ApiResponse<Product>(
success = false,
message = "Internal server error: ${e.message}"
)
)
}
}
}
场景2:文件下载
kotlin
@GetMapping("/download/{filename}")
fun downloadFile(@PathVariable filename: String): ResponseEntity<Resource> {
return try {
val file = fileService.getFile(filename)
val resource = FileSystemResource(file)
if (!resource.exists()) {
return ResponseEntity.notFound().build()
}
ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"$filename\"")
.contentLength(file.length())
.body(resource)
} catch (e: Exception) {
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build()
}
}
场景3:分页数据响应
kotlin
@GetMapping("/api/users")
fun getUsers(
@RequestParam(defaultValue = "0") page: Int,
@RequestParam(defaultValue = "10") size: Int
): ResponseEntity<Map<String, Any>> {
val pageable = PageRequest.of(page, size)
val userPage = userService.findAll(pageable)
val response = mapOf(
"users" to userPage.content,
"currentPage" to userPage.number,
"totalPages" to userPage.totalPages,
"totalElements" to userPage.totalElements,
"hasNext" to userPage.hasNext(),
"hasPrevious" to userPage.hasPrevious()
)
return ResponseEntity.ok()
.header("X-Total-Count", userPage.totalElements.toString())
.header("X-Page-Number", userPage.number.toString())
.body(response)
}
ResponseEntity 的构建方式
1. 静态方法构建(推荐)
kotlin
// 成功响应
ResponseEntity.ok(data) // 200 OK
ResponseEntity.ok().build() // 200 OK,无响应体
// 创建响应
ResponseEntity.status(HttpStatus.CREATED).body(data) // 201 Created
// 错误响应
ResponseEntity.notFound().build() // 404 Not Found
ResponseEntity.badRequest().build() // 400 Bad Request
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build() // 500
// 无内容响应
ResponseEntity.noContent().build() // 204 No Content
2. 构建器模式
kotlin
ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.header("Custom-Header", "value")
.eTag("\"123456\"")
.cacheControl(CacheControl.maxAge(Duration.ofHours(1)))
.body(data)
异步响应支持
Spring MVC 还支持异步响应,适用于需要长时间处理的请求:
kotlin
@GetMapping("/async-data")
fun getAsyncData(): ResponseEntity<Mono<String>> {
val asyncData = Mono.fromCallable {
// 模拟耗时操作
Thread.sleep(2000)
"Async data processed"
}.subscribeOn(Schedulers.boundedElastic())
return ResponseEntity.ok()
.header("X-Processing", "async")
.body(asyncData)
}
最佳实践建议
IMPORTANT
统一响应格式:在团队开发中,建议定义统一的 API 响应格式,确保前后端接口的一致性。
TIP
合理使用状态码:
- 200 OK:成功获取资源
- 201 Created:成功创建资源
- 204 No Content:成功执行但无返回内容
- 400 Bad Request:请求参数错误
- 404 Not Found:资源不存在
- 500 Internal Server Error:服务器内部错误
WARNING
避免过度使用:不是所有接口都需要使用 ResponseEntity,简单的数据返回使用 @ResponseBody
即可。
总结
ResponseEntity
是 Spring MVC 中构建完整 HTTP 响应的强大工具,它让我们能够:
✅ 精确控制 HTTP 状态码
✅ 灵活设置响应头
✅ 统一 API 响应格式
✅ 处理复杂的业务场景
✅ 支持异步响应
通过合理使用 ResponseEntity
,我们可以构建更加规范、灵活和用户友好的 REST API。记住,好的 API 设计不仅要功能正确,还要语义清晰、易于理解和使用。
实践建议
在实际项目中,建议结合全局异常处理器(@ControllerAdvice
)和 ResponseEntity
一起使用,可以构建更加完善的 API 响应体系。