Skip to content

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 解决的核心问题

  1. 自动反序列化:无需手动解析 JSON/XML
  2. 类型安全:编译时就能发现类型错误
  3. 代码简洁:大幅减少样板代码
  4. 响应式支持:完美集成 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))
        }
}

最佳实践与注意事项

✅ 推荐做法

性能优化建议

  1. 合理选择响应式类型:单个对象用 Mono,集合或流用 Flux
  2. 设置合理的内存限制:防止大请求体导致内存溢出
  3. 使用背压处理:在处理大量数据时控制处理速度
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) // 设置背压缓冲区
}

⚠️ 常见陷阱

注意事项

  1. 验证参数的位置:使用 Errors 参数时,@RequestBody 不能是响应式类型
  2. 内存管理:大文件上传时要特别注意内存使用
  3. 错误处理:响应式流中的错误处理需要特别注意
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 响应式编程的核心技能之一! 🎉