Appearance
WebClient Attributes:请求级别的数据传递机制 🎯
什么是 WebClient Attributes?
WebClient Attributes 是 Spring WebFlux 中一个强大而优雅的特性,它允许我们在 HTTP 请求中附加自定义属性,这些属性可以在整个请求处理链路中传递和访问。
NOTE
可以把 Attributes 理解为请求的"行李标签",每个请求都可以携带一些额外的信息,这些信息会伴随请求在整个处理流程中传递。
核心价值与应用场景 💡
解决的痛点
在传统的 HTTP 客户端中,我们经常遇到这样的问题:
kotlin
// 传统方式:需要在多个地方重复传递相同的信息
class UserService {
fun getUserData(userId: String, requestId: String) {
// 需要在每个方法中都传递 requestId
val profile = getUserProfile(userId, requestId)
val preferences = getUserPreferences(userId, requestId)
// ... 更多调用
}
private fun getUserProfile(userId: String, requestId: String) {
// 又要传递 requestId 用于日志追踪
logger.info("[$requestId] Fetching user profile for $userId")
// HTTP 调用...
}
}
kotlin
// 使用 Attributes:一次设置,全链路可用
class UserService {
fun getUserData(userId: String, requestId: String) {
webClient.get()
.uri("/api/user/$userId/profile")
.attribute("requestId", requestId)
.attribute("userId", userId)
.retrieve()
.bodyToMono<UserProfile>()
}
}
主要应用场景
- 请求追踪与日志关联 📊
- 认证信息传递 🔐
- 业务上下文传递 🏢
- 性能监控与统计 📈
技术原理深度解析 🔍
IMPORTANT
Attributes 只存在于客户端的请求处理链中,不会作为 HTTP 头部发送到服务器。它们是纯粹的客户端上下文信息。
实战代码示例 💻
基础用法:请求追踪
kotlin
@Service
class OrderService(private val webClient: WebClient) {
suspend fun processOrder(orderId: String): OrderResult {
val traceId = UUID.randomUUID().toString()
return webClient.post()
.uri("/api/orders/$orderId/process")
.attribute("traceId", traceId)
.attribute("operation", "processOrder")
.attribute("startTime", System.currentTimeMillis())
.retrieve()
.awaitBody<OrderResult>()
}
}
高级用法:全局过滤器配置
kotlin
@Configuration
class WebClientConfig {
@Bean
fun webClient(): WebClient {
return WebClient.builder()
.filter(loggingFilter())
.filter(metricsFilter())
.filter(authenticationFilter())
.build()
}
private fun loggingFilter(): ExchangeFilterFunction {
return ExchangeFilterFunction.ofRequestProcessor { request ->
// 从属性中获取追踪信息
val traceId = request.attribute("traceId") as? String
val operation = request.attribute("operation") as? String
logger.info("[$traceId] Starting $operation - ${request.method()} ${request.url()}")
Mono.just(request)
}
}
private fun metricsFilter(): ExchangeFilterFunction {
return ExchangeFilterFunction.ofRequestProcessor { request ->
val startTime = request.attribute("startTime") as? Long
if (startTime != null) {
val duration = System.currentTimeMillis() - startTime
// 记录性能指标
meterRegistry.timer("http.client.duration")
.record(duration, TimeUnit.MILLISECONDS)
}
Mono.just(request)
}
}
}
业务场景:多租户支持
kotlin
@Service
class MultiTenantApiClient(private val webClient: WebClient) {
suspend fun fetchTenantData(tenantId: String, dataType: String): ApiResponse {
return webClient.get()
.uri("/api/data/$dataType")
.attribute("tenantId", tenantId)
.attribute("dataType", dataType)
.attribute("requestTime", Instant.now())
.retrieve()
.awaitBody<ApiResponse>()
}
}
// 对应的过滤器
@Component
class TenantFilter : ExchangeFilterFunction {
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
val tenantId = request.attribute("tenantId") as? String
return if (tenantId != null) {
// 为请求添加租户相关的头部
val modifiedRequest = ClientRequest.from(request)
.header("X-Tenant-ID", tenantId)
.build()
next.exchange(modifiedRequest)
} else {
next.exchange(request)
}
}
}
全局默认属性配置 🌍
Spring 提供了在 WebClient 构建时设置默认属性的能力:
kotlin
@Configuration
class GlobalWebClientConfig {
@Bean
fun webClient(): WebClient {
return WebClient.builder()
.defaultRequest { requestSpec ->
requestSpec
.attribute("applicationName", "order-service")
.attribute("version", "1.0.0")
.attribute("environment", activeProfile)
}
.build()
}
@Value("${spring.profiles.active:default}")
private lateinit var activeProfile: String
}
TIP
在 Spring MVC 应用中,你可以利用 ThreadLocal
数据来动态设置默认属性,比如从当前请求的安全上下文中提取用户信息。
与 Spring MVC 集成的实际案例 🔗
kotlin
@RestController
class OrderController(private val orderApiClient: OrderApiClient) {
@GetMapping("/orders/{orderId}")
suspend fun getOrder(
@PathVariable orderId: String,
request: HttpServletRequest
): OrderDto {
// 从当前 HTTP 请求中提取信息
val userAgent = request.getHeader("User-Agent")
val clientIp = request.remoteAddr
val sessionId = request.session.id
return orderApiClient.fetchOrder(orderId, userAgent, clientIp, sessionId)
}
}
@Service
class OrderApiClient(private val webClient: WebClient) {
suspend fun fetchOrder(
orderId: String,
userAgent: String?,
clientIp: String?,
sessionId: String?
): OrderDto {
return webClient.get()
.uri("/external-api/orders/$orderId")
.attribute("userAgent", userAgent)
.attribute("clientIp", clientIp)
.attribute("sessionId", sessionId)
.attribute("internalOrderId", orderId)
.retrieve()
.awaitBody<OrderDto>()
}
}
最佳实践与注意事项 ⚡
属性命名约定
kotlin
object AttributeKeys {
const val TRACE_ID = "traceId"
const val USER_ID = "userId"
const val TENANT_ID = "tenantId"
const val REQUEST_START_TIME = "requestStartTime"
const val OPERATION_NAME = "operationName"
}
// 使用常量而不是魔法字符串
webClient.get()
.uri("/api/data")
.attribute(AttributeKeys.TRACE_ID, traceId)
.attribute(AttributeKeys.USER_ID, userId)
.retrieve()
.awaitBody<DataResponse>()
类型安全的属性访问
kotlin
// 创建类型安全的属性访问器
class RequestAttributes(private val request: ClientRequest) {
fun getTraceId(): String? = request.attribute("traceId") as? String
fun getUserId(): String? = request.attribute("userId") as? String
fun getStartTime(): Long? = request.attribute("startTime") as? Long
// 提供默认值的便捷方法
fun getTraceIdOrGenerate(): String =
getTraceId() ?: UUID.randomUUID().toString()
}
// 在过滤器中使用
private fun createLoggingFilter(): ExchangeFilterFunction {
return ExchangeFilterFunction.ofRequestProcessor { request ->
val attrs = RequestAttributes(request)
val traceId = attrs.getTraceIdOrGenerate()
logger.info("[$traceId] Processing request: ${request.url()}")
Mono.just(request)
}
}
WARNING
属性值的类型转换需要谨慎处理,建议使用安全的类型转换(as?
)并提供合理的默认值。
CAUTION
不要在属性中存储大量数据或敏感信息,属性主要用于传递轻量级的上下文信息。
总结 📝
WebClient Attributes 是一个看似简单但功能强大的特性,它解决了在响应式 HTTP 客户端中传递上下文信息的难题。通过合理使用 Attributes,我们可以:
- ✅ 实现优雅的请求追踪和日志关联
- ✅ 简化多层服务调用中的参数传递
- ✅ 支持复杂的业务场景(如多租户、A/B 测试)
- ✅ 提供更好的可观测性和监控能力
记住核心理念
Attributes 的设计哲学是"一次设置,全链路可用",它让我们能够在不污染业务逻辑的前提下,优雅地处理横切关注点。
掌握了 WebClient Attributes,你就拥有了构建健壮、可维护的响应式 HTTP 客户端的重要工具! 🚀