Skip to content

Spring WebFlux @RequestAttribute 注解详解

📚 概述

在 Spring WebFlux 的响应式编程世界中,@RequestAttribute 注解是一个强大而实用的工具,它允许我们在控制器方法中直接访问预先存储在请求作用域中的属性。这些属性通常由过滤器、拦截器或其他中间件组件在请求处理的早期阶段创建。

NOTE

@RequestAttribute@SessionAttribute 类似,但作用域不同:前者针对单个请求,后者针对整个会话。

🎯 核心价值与应用场景

解决的核心问题

想象一下这样的场景:在一个微服务架构中,每个请求都需要携带用户身份信息、请求追踪ID、安全上下文等数据。如果没有 @RequestAttribute,我们可能需要:

  • 在每个控制器方法中手动从 ServerHttpRequest 中提取这些信息
  • 重复编写相同的数据获取逻辑
  • 处理复杂的类型转换和空值检查

@RequestAttribute 的设计哲学是简化数据传递,提升代码可读性,让开发者专注于业务逻辑而非基础设施代码。

典型应用场景

常见用途

  • 用户身份验证:从认证过滤器中获取用户信息
  • 请求追踪:获取分布式追踪的 TraceId
  • 安全上下文:访问权限验证后的安全信息
  • 业务上下文:获取租户信息、地域信息等

🔧 技术原理深度解析

工作机制

属性存储与检索原理

在 Spring WebFlux 中,请求属性存储在 ServerWebExchange 的属性映射中:

kotlin
// 底层存储机制(简化版)
class ServerWebExchangeImpl {
    private val attributes: MutableMap<String, Any> = ConcurrentHashMap()
    
    fun getAttribute(name: String): Any? = attributes[name]
    fun setAttribute(name: String, value: Any) {
        attributes[name] = value
    }
}

💡 实战代码示例

基础用法示例

kotlin
// 1. 创建用户信息数据类
data class UserInfo(
    val userId: String,
    val username: String,
    val roles: List<String>
)

// 2. 认证过滤器 - 提取并存储用户信息
@Component
class AuthenticationFilter : WebFilter {
    
    override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
        val request = exchange.request
        val token = request.headers.getFirst("Authorization")
        
        return if (token != null) {
            // 解析 token 获取用户信息
            val userInfo = parseToken(token) 
            
            // 将用户信息存储到请求属性中
            exchange.attributes["currentUser"] = userInfo 
            
            chain.filter(exchange)
        } else {
            // 处理未认证请求
            exchange.response.statusCode = HttpStatus.UNAUTHORIZED
            exchange.response.setComplete()
        }
    }
    
    private fun parseToken(token: String): UserInfo {
        // 模拟 token 解析逻辑
        return UserInfo(
            userId = "12345",
            username = "john_doe",
            roles = listOf("USER", "ADMIN")
        )
    }
}

// 3. 控制器 - 使用 @RequestAttribute 获取用户信息
@RestController
@RequestMapping("/api")
class UserController {
    
    @GetMapping("/profile")
    fun getUserProfile(
        @RequestAttribute("currentUser") user: UserInfo
    ): Mono<Map<String, Any>> {
        return Mono.just(mapOf(
            "userId" to user.userId,
            "username" to user.username,
            "roles" to user.roles,
            "message" to "欢迎, ${user.username}!"
        ))
    }
    
    @PostMapping("/orders")
    fun createOrder(
        @RequestAttribute("currentUser") user: UserInfo, 
        @RequestBody orderRequest: OrderRequest
    ): Mono<OrderResponse> {
        // 使用用户信息处理订单创建逻辑
        return orderService.createOrder(user.userId, orderRequest)
    }
}
kotlin
// 1. 追踪信息数据类
data class TraceContext(
    val traceId: String,
    val spanId: String,
    val timestamp: Long = System.currentTimeMillis()
)

// 2. 追踪过滤器
@Component
class TracingFilter : WebFilter {
    
    override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
        val traceId = exchange.request.headers.getFirst("X-Trace-ID") 
            ?: generateTraceId()
        
        val traceContext = TraceContext(
            traceId = traceId,
            spanId = generateSpanId()
        )
        
        // 存储追踪上下文
        exchange.attributes["traceContext"] = traceContext 
        
        return chain.filter(exchange)
            .doFinally {
                // 清理追踪上下文
                logRequestCompletion(traceContext)
            }
    }
    
    private fun generateTraceId(): String = UUID.randomUUID().toString()
    private fun generateSpanId(): String = UUID.randomUUID().toString().substring(0, 8)
    
    private fun logRequestCompletion(context: TraceContext) {
        val duration = System.currentTimeMillis() - context.timestamp
        println("请求完成 - TraceID: ${context.traceId}, 耗时: ${duration}ms")
    }
}

// 3. 业务控制器
@RestController
class BusinessController {
    
    @GetMapping("/data")
    fun getData(
        @RequestAttribute("traceContext") trace: TraceContext
    ): Mono<Map<String, Any>> {
        println("处理请求 - TraceID: ${trace.traceId}")
        
        return Mono.just(mapOf(
            "data" to "业务数据",
            "traceId" to trace.traceId,
            "processTime" to System.currentTimeMillis()
        ))
    }
}

高级用法:可选属性与默认值

kotlin
@RestController
class AdvancedController {
    
    // 可选属性 - 使用 required = false
    @GetMapping("/optional")
    fun handleOptional(
        @RequestAttribute(value = "optionalData", required = false) 
        data: String? 
    ): Mono<String> {
        return Mono.just(data ?: "使用默认值")
    }
    
    // 复杂对象属性
    @GetMapping("/complex")
    fun handleComplex(
        @RequestAttribute("userContext") userContext: UserContext,
        @RequestAttribute("requestMetadata") metadata: RequestMetadata
    ): Mono<ResponseEntity<Any>> {
        
        return if (userContext.hasPermission("READ_DATA")) {
            Mono.just(ResponseEntity.ok(mapOf(
                "data" to "敏感业务数据",
                "requestId" to metadata.requestId
            )))
        } else {
            Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN)
                .body("权限不足"))
        }
    }
}

// 支持数据类
data class UserContext(
    val user: UserInfo,
    val permissions: Set<String>
) {
    fun hasPermission(permission: String): Boolean = permissions.contains(permission)
}

data class RequestMetadata(
    val requestId: String,
    val clientIp: String,
    val userAgent: String
)

⚠️ 常见陷阱与最佳实践

常见错误

属性不存在异常

当请求属性不存在且 required = true(默认)时,Spring 会抛出异常。

kotlin
// ❌ 错误示例 - 可能导致异常
@GetMapping("/risky")
fun riskyMethod(
    @RequestAttribute("nonExistentAttr") data: String
) = Mono.just("这可能会失败")

// ✅ 正确示例 - 安全处理
@GetMapping("/safe")
fun safeMethod(
    @RequestAttribute(value = "maybeExists", required = false) 
    data: String? 
) = Mono.just(data ?: "安全的默认值")

最佳实践

开发建议

  1. 明确属性命名:使用清晰、一致的属性名称
  2. 类型安全:尽量使用强类型而非 Any 类型
  3. 空值处理:合理使用 required = false 和可空类型
  4. 文档化:为自定义属性编写清晰的文档
kotlin
// 推荐的属性常量管理
object RequestAttributes {
    const val CURRENT_USER = "currentUser"
    const val TRACE_CONTEXT = "traceContext"
    const val TENANT_INFO = "tenantInfo"
    const val REQUEST_METADATA = "requestMetadata"
}

// 使用常量
@GetMapping("/best-practice")
fun bestPracticeExample(
    @RequestAttribute(RequestAttributes.CURRENT_USER) user: UserInfo, 
    @RequestAttribute(RequestAttributes.TRACE_CONTEXT) trace: TraceContext
): Mono<String> {
    return Mono.just("最佳实践示例")
}

🔄 与其他注解的对比

注解作用域数据来源生命周期
@RequestAttribute单个请求过滤器/拦截器设置请求期间
@SessionAttribute用户会话会话中存储的数据会话期间
@RequestParam单个请求URL 查询参数请求期间
@RequestHeader单个请求HTTP 请求头请求期间

🎉 总结

@RequestAttribute 注解是 Spring WebFlux 中一个优雅的解决方案,它通过简化请求作用域数据的访问,让我们能够:

  • 提升代码可读性:直接在方法参数中声明需要的数据
  • 减少样板代码:无需手动从请求中提取属性
  • 增强类型安全:编译时类型检查,减少运行时错误
  • 改善可维护性:集中管理请求级别的共享数据

通过合理使用 @RequestAttribute,我们可以构建更加清晰、高效的响应式 Web 应用程序! 🚀