Appearance
Spring WebFlux 中的 @RequestBody 注解详解 🚀
什么是 @RequestBody?
@RequestBody
是 Spring WebFlux 中用于处理 HTTP 请求体的核心注解。它能够将客户端发送的请求体数据(如 JSON、XML 等)自动反序列化为 Java 对象,让我们能够轻松处理复杂的数据结构。
NOTE
在 WebFlux 中,@RequestBody
不仅支持传统的阻塞式处理,还完全支持响应式编程模型,这是它与 Spring MVC 的重要区别。
为什么需要 @RequestBody?
传统方式的痛点 😵
在没有 @RequestBody
的时代,我们需要手动处理请求体:
kotlin
@PostMapping("/accounts")
fun createAccount(request: ServerHttpRequest): Mono<String> {
return request.body
.map { dataBuffer ->
// 手动读取字节流
val bytes = ByteArray(dataBuffer.readableByteCount())
dataBuffer.read(bytes)
String(bytes)
}
.collectList()
.map { bodyParts ->
val jsonString = bodyParts.joinToString("")
// 手动解析 JSON
val objectMapper = ObjectMapper()
objectMapper.readValue(jsonString, Account::class.java)
}
.map { account ->
// 处理业务逻辑
"Account created: ${account.name}"
}
}
kotlin
@PostMapping("/accounts")
fun createAccount(@RequestBody account: Account): Mono<String> {
// 直接使用反序列化后的对象
return Mono.just("Account created: ${account.name}")
}
@RequestBody 解决的核心问题
- 自动反序列化:无需手动解析 JSON/XML
- 类型安全:编译时就能发现类型错误
- 代码简洁:大幅减少样板代码
- 响应式支持:完美集成 WebFlux 的非阻塞特性
基础用法示例
1. 简单对象绑定
kotlin
// 数据模型
data class Account(
val id: Long? = null,
val name: String,
val email: String,
val balance: Double = 0.0
)
@RestController
@RequestMapping("/api/accounts")
class AccountController {
@PostMapping
fun createAccount(@RequestBody account: Account): Mono<Account> {
// Spring 自动将请求体 JSON 转换为 Account 对象
return accountService.save(account)
}
}
请求示例:
bash
POST /api/accounts
Content-Type: application/json
{
"name": "张三",
"email": "[email protected]",
"balance": 1000.0
}
2. 响应式类型支持
WebFlux 的强大之处在于对响应式类型的原生支持:
kotlin
@PostMapping("/batch")
fun createAccountsBatch(@RequestBody accounts: Flux<Account>): Flux<Account> {
// 处理流式数据,支持大量数据的非阻塞处理
return accounts
.doOnNext { account ->
println("Processing account: ${account.name}")
}
.flatMap { account ->
accountService.save(account)
}
}
@PostMapping("/single")
fun createSingleAccount(@RequestBody account: Mono<Account>): Mono<Account> {
// 处理单个对象的异步版本
return account
.doOnNext { acc ->
println("Received account: ${acc.name}")
}
.flatMap { acc ->
accountService.save(acc)
}
}
TIP
使用 Mono<T>
适合处理单个对象,Flux<T>
适合处理对象流。这种设计让我们能够处理大量数据而不会阻塞线程。
数据流处理示例
让我们通过一个实际的业务场景来理解 WebFlux 中 @RequestBody
的威力:
kotlin
@PostMapping("/batch-stream")
fun processBatchAccounts(@RequestBody accounts: Flux<Account>): Flux<AccountResponse> {
return accounts
.index() // 添加索引,用于跟踪处理进度
.flatMap { (index, account) ->
// 并行处理,但限制并发数
accountService.validateAndSave(account)
.map { savedAccount ->
AccountResponse(
success = true,
account = savedAccount,
index = index
)
}
.onErrorResume { error ->
// 错误处理,不中断整个流
Mono.just(AccountResponse(
success = false,
error = error.message,
index = index
))
}
}
.doOnNext { response ->
println("Processed item ${response.index}: ${response.success}")
}
}
data class AccountResponse(
val success: Boolean,
val account: Account? = null,
val error: String? = null,
val index: Long
)
数据验证集成
基础验证
kotlin
import jakarta.validation.Valid
import jakarta.validation.constraints.*
data class Account(
val id: Long? = null,
@field:NotBlank(message = "姓名不能为空")
@field:Size(min = 2, max = 50, message = "姓名长度必须在2-50个字符之间")
val name: String,
@field:Email(message = "邮箱格式不正确")
@field:NotBlank(message = "邮箱不能为空")
val email: String,
@field:DecimalMin(value = "0.0", message = "余额不能为负数")
val balance: Double = 0.0
)
@PostMapping("/validated")
fun createValidatedAccount(@Valid @RequestBody account: Account): Mono<Account> {
// 如果验证失败,会自动抛出 WebExchangeBindException
return accountService.save(account)
}
响应式验证处理
kotlin
@PostMapping("/validated-reactive")
fun createValidatedAccountReactive(
@Valid @RequestBody account: Mono<Account>
): Mono<ResponseEntity<Any>> {
return account
.flatMap { acc -> accountService.save(acc) }
.map { savedAccount ->
ResponseEntity.ok(savedAccount)
}
.onErrorResume { error ->
when (error) {
is WebExchangeBindException -> {
// 处理验证错误
val errors = error.bindingResult.allErrors.map {
it.defaultMessage
}
Mono.just(ResponseEntity.badRequest().body(
mapOf("errors" to errors)
))
}
else -> {
Mono.just(ResponseEntity.status(500).body(
mapOf("error" to "Internal server error")
))
}
}
}
}
自定义错误处理
kotlin
@PostMapping("/custom-validation")
fun createAccountWithCustomValidation(
@Valid @RequestBody account: Account,
errors: Errors
): Mono<ResponseEntity<Any>> {
// 注意:使用 Errors 参数时,@RequestBody 不能是 Mono 类型
if (errors.hasErrors()) {
val errorMessages = errors.allErrors.map { error ->
when (error) {
is FieldError -> "${error.field}: ${error.defaultMessage}"
else -> error.defaultMessage ?: "Unknown error"
}
}
return Mono.just(ResponseEntity.badRequest().body(
mapOf(
"success" to false,
"errors" to errorMessages
)
))
}
return accountService.save(account)
.map { savedAccount ->
ResponseEntity.ok(mapOf(
"success" to true,
"data" to savedAccount
))
}
}
高级特性
1. 自定义消息转换器
kotlin
@Configuration
class WebFluxConfig : WebFluxConfigurer {
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
// 配置 JSON 处理
configurer.defaultCodecs().jackson2JsonDecoder(
Jackson2JsonDecoder(ObjectMapper().apply {
propertyNamingStrategy = PropertyNamingStrategies.SNAKE_CASE
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
})
)
// 设置内存限制
configurer.defaultCodecs().maxInMemorySize(1024 * 1024) // 1MB
}
}
2. 条件化处理
kotlin
@PostMapping("/conditional")
fun conditionalProcessing(
@RequestBody request: Mono<Map<String, Any>>
): Mono<ResponseEntity<String>> {
return request
.flatMap { data ->
when (data["type"]) {
"account" -> {
// 转换为 Account 对象
val account = objectMapper.convertValue(data, Account::class.java)
accountService.save(account)
.map { "Account created: ${it.name}" }
}
"user" -> {
// 转换为 User 对象
val user = objectMapper.convertValue(data, User::class.java)
userService.save(user)
.map { "User created: ${it.username}" }
}
else -> Mono.error(IllegalArgumentException("Unknown type"))
}
}
.map { message -> ResponseEntity.ok(message) }
.onErrorResume { error ->
Mono.just(ResponseEntity.badRequest().body(error.message))
}
}
最佳实践与注意事项
✅ 推荐做法
性能优化建议
- 合理选择响应式类型:单个对象用
Mono
,集合或流用Flux
- 设置合理的内存限制:防止大请求体导致内存溢出
- 使用背压处理:在处理大量数据时控制处理速度
kotlin
// 好的做法:使用背压控制
@PostMapping("/large-batch")
fun processLargeBatch(@RequestBody accounts: Flux<Account>): Flux<String> {
return accounts
.buffer(100) // 分批处理,每批100个
.flatMap { batch ->
accountService.saveBatch(batch)
.collectList()
.map { savedAccounts ->
"Processed batch of ${savedAccounts.size} accounts"
}
}
.onBackpressureBuffer(1000) // 设置背压缓冲区
}
⚠️ 常见陷阱
注意事项
- 验证参数的位置:使用
Errors
参数时,@RequestBody
不能是响应式类型 - 内存管理:大文件上传时要特别注意内存使用
- 错误处理:响应式流中的错误处理需要特别注意
kotlin
// 错误的做法
@PostMapping("/wrong")
fun wrongValidation(
@Valid @RequestBody account: Mono<Account>,
errors: Errors
): Mono<String> {
// 这样写会导致编译错误!
// 使用 Errors 参数时,@RequestBody 必须是非响应式类型
}
// 正确的做法
@PostMapping("/correct")
fun correctValidation(
@Valid @RequestBody account: Account,
errors: Errors
): Mono<String> {
if (errors.hasErrors()) {
return Mono.error(IllegalArgumentException("Validation failed"))
}
return accountService.save(account).map { "Success" }
}
总结
@RequestBody
在 Spring WebFlux 中不仅仅是一个简单的注解,它是连接 HTTP 世界和 Java 对象世界的桥梁。通过支持响应式类型,它让我们能够:
- 🚀 高性能:非阻塞处理大量并发请求
- 🔄 流式处理:处理大量数据而不会内存溢出
- 🛡️ 类型安全:编译时发现错误,运行时更稳定
- 🎯 简洁优雅:用最少的代码实现最复杂的功能
掌握 @RequestBody
的各种用法,就是掌握了 WebFlux 响应式编程的核心技能之一! 🎉