Skip to content

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 响应体系。