Appearance
Spring WebFlux @RequestHeader 注解详解 🚀
什么是 @RequestHeader?
@RequestHeader
是 Spring WebFlux 中用于绑定 HTTP 请求头到控制器方法参数的注解。它让我们能够轻松地从客户端请求中提取和使用各种头部信息。
NOTE
在 Web 开发中,HTTP 头部承载着丰富的元数据信息,如客户端类型、编码格式、认证信息等。@RequestHeader 让我们能够优雅地访问这些关键信息。
为什么需要 @RequestHeader? 🤔
解决的核心痛点
在没有 @RequestHeader
之前,开发者需要手动从 ServerHttpRequest
对象中提取头部信息,这种方式存在以下问题:
kotlin
@GetMapping("/demo")
fun handleOldWay(request: ServerHttpRequest): Mono<String> {
// 手动提取头部信息,代码冗长
val headers = request.headers
val encoding = headers.getFirst("Accept-Encoding") ?: "default"
val keepAlive = headers.getFirst("Keep-Alive")?.toLongOrNull() ?: 0L
// 需要手动处理空值和类型转换
return Mono.just("Processed with encoding: $encoding, keepAlive: $keepAlive")
}
kotlin
@GetMapping("/demo")
fun handle(
@RequestHeader("Accept-Encoding") encoding: String,
@RequestHeader("Keep-Alive") keepAlive: Long
): Mono<String> {
// 直接使用,Spring 自动处理类型转换和绑定
return Mono.just("Processed with encoding: $encoding, keepAlive: $keepAlive")
}
HTTP 请求头基础知识 📋
让我们先了解一个典型的 HTTP 请求头结构:
基础用法示例 💡
1. 单个头部绑定
kotlin
@RestController
class HeaderController {
@GetMapping("/basic-header")
fun handleBasicHeader(
@RequestHeader("User-Agent") userAgent: String
): Mono<Map<String, Any>> {
return Mono.just(mapOf(
"message" to "请求处理成功",
"userAgent" to userAgent,
"timestamp" to System.currentTimeMillis()
))
}
}
2. 多个头部同时绑定
kotlin
@GetMapping("/multiple-headers")
fun handleMultipleHeaders(
@RequestHeader("Accept-Encoding") encoding: String,
@RequestHeader("Keep-Alive") keepAlive: Long,
@RequestHeader("Accept-Language") language: String
): Mono<ResponseEntity<Map<String, Any>>> {
val responseData = mapOf(
"encoding" to encoding,
"keepAlive" to keepAlive,
"language" to language,
"processed" to true
)
return Mono.just(ResponseEntity.ok(responseData))
}
高级特性详解 🔥
1. 自动类型转换
Spring WebFlux 提供强大的类型转换能力:
kotlin
@GetMapping("/type-conversion")
fun handleTypeConversion(
@RequestHeader("Content-Length") contentLength: Int,
@RequestHeader("Keep-Alive") keepAlive: Long,
@RequestHeader("X-Custom-Flag") customFlag: Boolean,
@RequestHeader("X-Rate-Limit") rateLimit: Double
): Mono<String> {
return Mono.just("""
Content Length: $contentLength (Int)
Keep Alive: $keepAlive (Long)
Custom Flag: $customFlag (Boolean)
Rate Limit: $rateLimit (Double)
""".trimIndent())
}
TIP
Spring 会自动将字符串类型的头部值转换为目标参数类型。如果转换失败,会抛出类型转换异常。
2. 可选头部处理
kotlin
@GetMapping("/optional-headers")
fun handleOptionalHeaders(
@RequestHeader(value = "Authorization", required = false)
authorization: String?,
@RequestHeader(value = "X-API-Version", defaultValue = "v1")
apiVersion: String
): Mono<Map<String, Any?>> {
return Mono.just(mapOf(
"isAuthenticated" to (authorization != null),
"apiVersion" to apiVersion,
"authHeader" to authorization
))
}
3. 集合类型头部
处理包含多个值的头部:
kotlin
@GetMapping("/collection-headers")
fun handleCollectionHeaders(
@RequestHeader("Accept") acceptTypes: List<String>,
@RequestHeader("Accept-Language") languages: Array<String>
): Mono<Map<String, Any>> {
return Mono.just(mapOf(
"acceptTypes" to acceptTypes,
"languages" to languages.toList(),
"primaryLanguage" to languages.firstOrNull()
))
}
4. 获取所有头部信息
kotlin
@GetMapping("/all-headers-map")
fun handleAllHeadersAsMap(
@RequestHeader headers: Map<String, String>
): Mono<Map<String, Any>> {
return Mono.just(mapOf(
"totalHeaders" to headers.size,
"headers" to headers
))
}
kotlin
@GetMapping("/all-headers-multivalue")
fun handleAllHeadersAsMultiValue(
@RequestHeader headers: MultiValueMap<String, String>
): Mono<Map<String, Any>> {
val processedHeaders = headers.mapValues { (_, values) ->
if (values.size == 1) values.first() else values
}
return Mono.just(mapOf(
"totalHeaders" to headers.size,
"headers" to processedHeaders
))
}
kotlin
@GetMapping("/all-headers-http")
fun handleAllHeadersAsHttpHeaders(
@RequestHeader headers: HttpHeaders
): Mono<Map<String, Any>> {
return Mono.just(mapOf(
"contentType" to headers.contentType?.toString(),
"contentLength" to headers.contentLength,
"host" to headers.host?.toString(),
"allHeaders" to headers.toSingleValueMap()
))
}
实际业务场景应用 🏢
1. API 版本控制
kotlin
@RestController
class VersionedApiController {
@GetMapping("/api/users")
fun getUsers(
@RequestHeader(value = "API-Version", defaultValue = "v1")
apiVersion: String
): Mono<ResponseEntity<List<Map<String, Any>>>> {
return when (apiVersion) {
"v1" -> getUsersV1()
"v2" -> getUsersV2()
else -> Mono.just(ResponseEntity.badRequest().build())
}
}
private fun getUsersV1(): Mono<ResponseEntity<List<Map<String, Any>>>> {
val users = listOf(
mapOf("id" to 1, "name" to "张三"),
mapOf("id" to 2, "name" to "李四")
)
return Mono.just(ResponseEntity.ok(users))
}
private fun getUsersV2(): Mono<ResponseEntity<List<Map<String, Any>>>> {
val users = listOf(
mapOf("id" to 1, "name" to "张三", "email" to "[email protected]"),
mapOf("id" to 2, "name" to "李四", "email" to "[email protected]")
)
return Mono.just(ResponseEntity.ok(users))
}
}
2. 认证和授权
kotlin
@RestController
class SecureController {
@GetMapping("/secure/profile")
fun getUserProfile(
@RequestHeader("Authorization") authorization: String
): Mono<ResponseEntity<Map<String, Any>>> {
return validateToken(authorization)
.flatMap { userId ->
getUserById(userId)
}
.map { user ->
ResponseEntity.ok(user)
}
.onErrorReturn(ResponseEntity.status(HttpStatus.UNAUTHORIZED).build())
}
private fun validateToken(authorization: String): Mono<String> {
return if (authorization.startsWith("Bearer ")) {
val token = authorization.substring(7)
// 模拟 token 验证逻辑
if (token == "valid-token-123") {
Mono.just("user123")
} else {
Mono.error(RuntimeException("Invalid token"))
}
} else {
Mono.error(RuntimeException("Invalid authorization format"))
}
}
private fun getUserById(userId: String): Mono<Map<String, Any>> {
return Mono.just(mapOf(
"id" to userId,
"name" to "用户名",
"email" to "[email protected]"
))
}
}
3. 国际化支持
kotlin
@RestController
class I18nController {
@GetMapping("/welcome")
fun getWelcomeMessage(
@RequestHeader(value = "Accept-Language", defaultValue = "zh-CN")
acceptLanguage: String
): Mono<Map<String, String>> {
val language = parseLanguage(acceptLanguage)
val message = getLocalizedMessage(language)
return Mono.just(mapOf(
"message" to message,
"language" to language
))
}
private fun parseLanguage(acceptLanguage: String): String {
// 解析 Accept-Language 头部,如 "zh-CN,zh;q=0.9,en;q=0.8"
return acceptLanguage.split(",")
.firstOrNull()
?.split(";")
?.firstOrNull()
?.trim() ?: "zh-CN"
}
private fun getLocalizedMessage(language: String): String {
return when (language.lowercase()) {
"zh-cn", "zh" -> "欢迎使用我们的服务!"
"en", "en-us" -> "Welcome to our service!"
"ja" -> "私たちのサービスへようこそ!"
else -> "欢迎使用我们的服务!"
}
}
}
异常处理和最佳实践 ⚠️
1. 头部缺失处理
kotlin
@RestController
class RobustHeaderController {
@GetMapping("/robust-headers")
fun handleRobustHeaders(
@RequestHeader(value = "X-Client-ID", required = false)
clientId: String?,
@RequestHeader(value = "X-Request-ID", defaultValue = "")
requestId: String
): Mono<ResponseEntity<Map<String, Any>>> {
// 生成默认的请求 ID
val finalRequestId = if (requestId.isBlank()) {
UUID.randomUUID().toString()
} else {
requestId
}
val response = mapOf(
"clientId" to (clientId ?: "anonymous"),
"requestId" to finalRequestId,
"timestamp" to System.currentTimeMillis()
)
return Mono.just(ResponseEntity.ok(response))
}
}
2. 全局异常处理
kotlin
@RestControllerAdvice
class HeaderExceptionHandler {
@ExceptionHandler(MissingRequestHeaderException::class)
fun handleMissingHeader(ex: MissingRequestHeaderException): ResponseEntity<Map<String, Any>> {
val error = mapOf(
"error" to "MISSING_HEADER",
"message" to "缺少必需的请求头: ${ex.headerName}",
"timestamp" to System.currentTimeMillis()
)
return ResponseEntity.badRequest().body(error)
}
@ExceptionHandler(TypeMismatchException::class)
fun handleTypeMismatch(ex: TypeMismatchException): ResponseEntity<Map<String, Any>> {
val error = mapOf(
"error" to "TYPE_CONVERSION_ERROR",
"message" to "请求头类型转换失败: ${ex.propertyName}",
"timestamp" to System.currentTimeMillis()
)
return ResponseEntity.badRequest().body(error)
}
}
性能优化建议 🚀
1. 避免过度使用 Map 接收所有头部
WARNING
当使用 @RequestHeader Map<String, String> headers
时,Spring 会解析所有请求头,这在头部信息很多时可能影响性能。
kotlin
// ❌ 不推荐:解析所有头部但只使用少数几个
@GetMapping("/inefficient")
fun inefficientWay(@RequestHeader headers: Map<String, String>): Mono<String> {
val userAgent = headers["User-Agent"]
val contentType = headers["Content-Type"]
return Mono.just("Processed")
}
// ✅ 推荐:只绑定需要的头部
@GetMapping("/efficient")
fun efficientWay(
@RequestHeader("User-Agent") userAgent: String,
@RequestHeader("Content-Type") contentType: String
): Mono<String> {
return Mono.just("Processed")
}
2. 合理使用默认值
kotlin
@GetMapping("/optimized")
fun optimizedHeaders(
@RequestHeader(value = "X-Rate-Limit", defaultValue = "100")
rateLimit: Int,
@RequestHeader(value = "X-Timeout", defaultValue = "30000")
timeout: Long
): Mono<String> {
// 避免了空值检查,提高代码执行效率
return Mono.just("Rate limit: $rateLimit, Timeout: $timeout")
}
测试示例 🧪
完整的测试用例示例
kotlin
@WebFluxTest(HeaderController::class)
class HeaderControllerTest {
@Autowired
private lateinit var webTestClient: WebTestClient
@Test
fun `should handle basic header correctly`() {
webTestClient.get()
.uri("/basic-header")
.header("User-Agent", "Mozilla/5.0")
.exchange()
.expectStatus().isOk
.expectBody()
.jsonPath("$.userAgent").isEqualTo("Mozilla/5.0")
}
@Test
fun `should handle multiple headers`() {
webTestClient.get()
.uri("/multiple-headers")
.header("Accept-Encoding", "gzip,deflate")
.header("Keep-Alive", "300")
.header("Accept-Language", "zh-CN,zh;q=0.9")
.exchange()
.expectStatus().isOk
.expectBody()
.jsonPath("$.encoding").isEqualTo("gzip,deflate")
.jsonPath("$.keepAlive").isEqualTo(300)
.jsonPath("$.language").isEqualTo("zh-CN,zh;q=0.9")
}
@Test
fun `should handle missing optional header`() {
webTestClient.get()
.uri("/optional-headers")
// 不提供 Authorization 头部
.exchange()
.expectStatus().isOk
.expectBody()
.jsonPath("$.isAuthenticated").isEqualTo(false)
.jsonPath("$.apiVersion").isEqualTo("v1") // 使用默认值
}
@Test
fun `should return error for missing required header`() {
webTestClient.get()
.uri("/basic-header")
// 不提供必需的 User-Agent 头部
.exchange()
.expectStatus().isBadRequest
}
}
总结 📝
@RequestHeader
注解是 Spring WebFlux 中处理 HTTP 请求头的强大工具,它提供了:
核心价值
- 简化代码:无需手动解析请求头
- 类型安全:自动类型转换和验证
- 灵活配置:支持可选头部和默认值
- 集成友好:与 Spring 生态系统完美集成
最佳实践总结 ✅
- 按需绑定:只绑定实际需要的头部,避免性能损失
- 合理默认:为可选头部提供合理的默认值
- 异常处理:实现全局异常处理器处理头部相关异常
- 类型转换:充分利用 Spring 的自动类型转换能力
- 测试覆盖:编写完整的测试用例验证头部处理逻辑
通过掌握 @RequestHeader
的使用,你可以构建更加健壮和用户友好的 WebFlux 应用程序! 🎉