mirror of
https://github.moeyy.xyz/https://github.com/GeWuYou/forgeboot
synced 2025-10-27 16:14:32 +08:00
feat(context): 重构上下文管理并添加序列化支持
- 新增 ContextHolder 类,替换原有的 StringContextHolder 对象 - 实现上下文值的序列化和反序列化 - 更新相关过滤器和装饰器以使用新的 ContextHolder- 添加获取指定类型值的方法- 实现 Context 接口的扩展方法,支持操作符重载
This commit is contained in:
parent
92b3eedb38
commit
7a6c371aa3
@ -26,7 +26,7 @@ abstract class AbstractContext<K, V>: Context<K, V> {
|
||||
* @param key 要查找的键
|
||||
* @return 对应的值,如果不存在则返回 null
|
||||
*/
|
||||
override fun get(key: K): V? {
|
||||
override fun retrieve(key: K): V? {
|
||||
return local.get()[key]
|
||||
}
|
||||
|
||||
|
||||
@ -21,7 +21,16 @@ interface Context<K, V> {
|
||||
* @param key 要查找的键
|
||||
* @return 对应的值,如果不存在则返回 null
|
||||
*/
|
||||
fun get(key: K): V?
|
||||
fun retrieve(key: K): V?
|
||||
|
||||
/**
|
||||
* 根据指定的键和类型从上下文中获取对应的值。
|
||||
*
|
||||
* @param key 要查找的键
|
||||
* @param type 要转换的目标类型
|
||||
* @return 对应类型的值,如果不存在或类型不匹配则返回 null
|
||||
*/
|
||||
fun <T> retrieveByType(key: K, type: Class<T>): T?
|
||||
|
||||
/**
|
||||
* 获取当前上下文的一个快照,包含所有键值对。
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
package com.gewuyou.forgeboot.context.api.extension
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.Context
|
||||
|
||||
/**
|
||||
* 通过操作符重载实现 Context 的 get 方法,用于根据指定的键获取对应的值。
|
||||
*
|
||||
* @param key 键,用于查找上下文中的值
|
||||
* @return 返回与键对应的值,如果不存在则返回 null
|
||||
*/
|
||||
operator fun <K, V> Context<K, V>.get(key: K): V? {
|
||||
return this.retrieve(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过操作符重载实现 Context 的 set 方法,用于将键值对存储到上下文中。
|
||||
*
|
||||
* @param key 键,用于标识要存储的值
|
||||
* @param value 要存储的值,可以为 null
|
||||
*/
|
||||
operator fun <K, V> Context<K, V>.set(key: K, value: V?) {
|
||||
this.put(key, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过操作符重载实现 Context 的 get 方法,用于根据指定的键和类型获取对应的值。
|
||||
*
|
||||
* @param key 键,用于查找上下文中的值
|
||||
* @param type 指定的值类型,用于类型安全地获取值
|
||||
* @return 返回与键和类型对应的值,如果不存在则返回 null
|
||||
*/
|
||||
operator fun <K, V, T> Context<K, V>.get(key: K, type: Class<T>) {
|
||||
this.retrieveByType(key, type)
|
||||
}
|
||||
@ -10,4 +10,5 @@ dependencies {
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
compileOnly(libs.springCloudStarter.openfeign)
|
||||
api(project(Modules.Core.SERIALIZATION))
|
||||
}
|
||||
|
||||
@ -1,17 +1,26 @@
|
||||
package com.gewuyou.forgeboot.context.autoconfigure
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.*
|
||||
import com.gewuyou.forgeboot.context.impl.*
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.gewuyou.forgeboot.context.api.ContextFieldContributor
|
||||
import com.gewuyou.forgeboot.context.api.ContextProcessor
|
||||
import com.gewuyou.forgeboot.context.api.FieldRegistry
|
||||
import com.gewuyou.forgeboot.context.impl.ContextHolder
|
||||
import com.gewuyou.forgeboot.context.impl.DefaultFieldRegistry
|
||||
import com.gewuyou.forgeboot.context.impl.filter.ContextServletFilter
|
||||
import com.gewuyou.forgeboot.context.impl.filter.ContextWebFilter
|
||||
import com.gewuyou.forgeboot.context.impl.processor.*
|
||||
|
||||
|
||||
import com.gewuyou.forgeboot.context.impl.processor.GeneratorProcessor
|
||||
import com.gewuyou.forgeboot.context.impl.processor.HeaderProcessor
|
||||
import com.gewuyou.forgeboot.context.impl.processor.MdcProcessor
|
||||
import com.gewuyou.forgeboot.context.impl.processor.ReactorProcessor
|
||||
import com.gewuyou.forgeboot.core.serialization.serializer.ValueSerializer
|
||||
import com.gewuyou.forgeboot.core.serialization.serializer.impl.serializer.JacksonValueSerializer
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.boot.autoconfigure.condition.*
|
||||
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer
|
||||
import org.springframework.core.task.TaskDecorator
|
||||
import org.springframework.web.reactive.function.client.ClientRequest
|
||||
|
||||
@ -26,6 +35,17 @@ import org.springframework.web.reactive.function.client.ClientRequest
|
||||
*/
|
||||
@Configuration
|
||||
class ForgeContextAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
fun valueSerializer(objectMapper: ObjectMapper): ValueSerializer{
|
||||
return JacksonValueSerializer(objectMapper)
|
||||
}
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
fun contextHolder(valueSerializer: ValueSerializer): ContextHolder {
|
||||
return ContextHolder(valueSerializer)
|
||||
}
|
||||
/* ───────────────────────────────────────────────────────────────
|
||||
0️⃣ 通用 Bean:不依赖 Web / Feign / Reactor 等外部包
|
||||
─────────────────────────────────────────────────────────────── */
|
||||
@ -171,13 +191,14 @@ class ForgeContextAutoConfiguration {
|
||||
* 拦截器会在每次 Feign 请求发起前,将当前上下文字段写入 HTTP 请求头。
|
||||
*
|
||||
* @param registry 字段注册表
|
||||
* @param contextHolder 上下文持有者
|
||||
* @return 构建完成的 feign.RequestInterceptor 实例
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
fun feignInterceptor(registry: FieldRegistry) =
|
||||
fun feignInterceptor(registry: FieldRegistry,contextHolder: ContextHolder) =
|
||||
feign.RequestInterceptor { tpl ->
|
||||
val ctx = StringContextHolder.snapshot()
|
||||
val ctx = contextHolder.snapshot()
|
||||
registry.all().forEach { def ->
|
||||
ctx[def.key]?.let { tpl.header(def.header, it) }
|
||||
}
|
||||
@ -200,20 +221,21 @@ class ForgeContextAutoConfiguration {
|
||||
* 通过装饰线程池任务,确保异步任务继承调用线程的上下文状态。
|
||||
*
|
||||
* @param processors 所有处理器列表
|
||||
* @param contextHolder 上下文持有者
|
||||
* @return 构建完成的 TaskDecorator 实例
|
||||
*/
|
||||
@Bean
|
||||
fun contextTaskDecorator(processors: List<ContextProcessor>) =
|
||||
fun contextTaskDecorator(processors: List<ContextProcessor>,contextHolder: ContextHolder) =
|
||||
TaskDecorator { delegate ->
|
||||
val snap = StringContextHolder.snapshot()
|
||||
val snap = contextHolder.snapshot()
|
||||
Runnable {
|
||||
try {
|
||||
snap.forEach(StringContextHolder::put)
|
||||
snap.forEach(contextHolder::put)
|
||||
processors.forEach { it.inject(Unit, snap.toMutableMap()) }
|
||||
delegate.run()
|
||||
} finally {
|
||||
processors.forEach { it.inject(Unit, mutableMapOf()) }
|
||||
StringContextHolder.clear()
|
||||
contextHolder.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -234,13 +256,13 @@ class ForgeContextAutoConfiguration {
|
||||
* 注册 WebClientCustomizer,用于定制 WebClient 的请求行为。
|
||||
*
|
||||
* 在每次请求发出前,将当前上下文字段写入 HTTP 请求头。
|
||||
*
|
||||
*@param contextHolder 上下文持有者
|
||||
* @return 构建完成的 WebClientCustomizer 实例
|
||||
*/
|
||||
@Bean
|
||||
fun contextWebClientCustomizer() = WebClientCustomizer { builder ->
|
||||
fun contextWebClientCustomizer(contextHolder: ContextHolder) = WebClientCustomizer { builder ->
|
||||
builder.filter { req, next ->
|
||||
val ctx = StringContextHolder.snapshot()
|
||||
val ctx = contextHolder.snapshot()
|
||||
val mutated = ClientRequest.from(req).apply {
|
||||
registry.all().forEach { def ->
|
||||
ctx[def.key]?.let { value -> header(def.header, value) }
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(Modules.Context.API))
|
||||
compileOnly(project(Modules.Core.SERIALIZATION))
|
||||
compileOnly(libs.springBootStarter.web)
|
||||
compileOnly(libs.springBootStarter.webflux)
|
||||
implementation(platform(libs.springBootDependencies.bom))
|
||||
implementation(libs.springBoot.autoconfigure)
|
||||
implementation(libs.jackson.databind)
|
||||
}
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
package com.gewuyou.forgeboot.context.impl
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.AbstractContext
|
||||
import com.gewuyou.forgeboot.core.serialization.serializer.ValueSerializer
|
||||
|
||||
/**
|
||||
* 上下文容器,用于存储和获取键值对形式的字符串数据。
|
||||
*
|
||||
* @since 2025-06-04 15:05:43
|
||||
* @author gewuyou
|
||||
*/
|
||||
open class ContextHolder(
|
||||
private val valueSerializer: ValueSerializer
|
||||
) : AbstractContext<String, String>() {
|
||||
|
||||
/**
|
||||
* 存储键值对到上下文中。如果值为 null,则不会存储。
|
||||
*
|
||||
* @param key 要存储的键
|
||||
* @param value 要存储的值,可以为 null
|
||||
*/
|
||||
open fun put(key: String, value: Any?) {
|
||||
super.put(key, value?.let { valueSerializer.serialize(it) })
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定的键和类型从上下文中获取对应的值。
|
||||
*
|
||||
* @param key 要查找的键
|
||||
* @param type 要转换的目标类型
|
||||
* @return 对应类型的值,如果不存在或类型不匹配则返回 null
|
||||
*/
|
||||
override fun <T> retrieveByType(key: String, type: Class<T>): T? {
|
||||
return retrieve(key)?.let { valueSerializer.deserialize(it, type) }
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
package com.gewuyou.forgeboot.context.impl
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.AbstractContext
|
||||
|
||||
/**
|
||||
*字符串上下文容器
|
||||
*
|
||||
* @since 2025-06-04 15:05:43
|
||||
* @author gewuyou
|
||||
*/
|
||||
object StringContextHolder: AbstractContext<String, String>()
|
||||
@ -0,0 +1,34 @@
|
||||
package com.gewuyou.forgeboot.context.impl.extension
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.Context
|
||||
|
||||
/**
|
||||
* 通过操作符重载实现 Context 的 get 方法,用于根据指定的键获取对应的值。
|
||||
*
|
||||
* @param key 键,用于查找上下文中的值
|
||||
* @return 返回与键对应的值,如果不存在则返回 null
|
||||
*/
|
||||
operator fun <K, V> Context<K, V>.get(key: K): V? {
|
||||
return this.retrieve(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过操作符重载实现 Context 的 set 方法,用于将键值对存储到上下文中。
|
||||
*
|
||||
* @param key 键,用于标识要存储的值
|
||||
* @param value 要存储的值,可以为 null
|
||||
*/
|
||||
operator fun <K, V> Context<K, V>.set(key: K, value: V?) {
|
||||
this.put(key, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过操作符重载实现 Context 的 get 方法,用于根据指定的键和类型获取对应的值。
|
||||
*
|
||||
* @param key 键,用于查找上下文中的值
|
||||
* @param type 指定的值类型,用于类型安全地获取值
|
||||
* @return 返回与键和类型对应的值,如果不存在则返回 null
|
||||
*/
|
||||
operator fun <K, V, T> Context<K, V>.get(key: K, type: Class<T>) {
|
||||
this.retrieveByType(key, type)
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.gewuyou.forgeboot.context.impl.extension
|
||||
|
||||
import com.gewuyou.forgeboot.context.impl.ContextHolder
|
||||
|
||||
/**
|
||||
* 为ContextHolder实现赋值操作符的重写方法
|
||||
*
|
||||
* @param key 存储值的键,用于后续从上下文获取值时使用
|
||||
* @param value 需要存储在ContextHolder中的值,允许为null
|
||||
*/
|
||||
operator fun ContextHolder.set(key: String, value: Any?) {
|
||||
this.put(key, value)
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
package com.gewuyou.forgeboot.context.impl.filter
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.ContextProcessor
|
||||
import com.gewuyou.forgeboot.context.impl.StringContextHolder
|
||||
import com.gewuyou.forgeboot.context.impl.ContextHolder
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
@ -20,6 +20,7 @@ import org.springframework.web.filter.OncePerRequestFilter
|
||||
*/
|
||||
class ContextServletFilter(
|
||||
private val chain: List<ContextProcessor>,
|
||||
private val contextHolder: ContextHolder
|
||||
) : OncePerRequestFilter() {
|
||||
|
||||
/**
|
||||
@ -38,13 +39,13 @@ class ContextServletFilter(
|
||||
filterChain: FilterChain,
|
||||
) {
|
||||
// 创建当前线程上下文快照的可变副本,确保后续操作不影响原始上下文
|
||||
val ctx = StringContextHolder.snapshot().toMutableMap()
|
||||
val ctx = contextHolder.snapshot().toMutableMap()
|
||||
|
||||
// 遍历上下文处理器链,依次从请求中提取上下文信息并更新临时上下文容器
|
||||
chain.forEach { it.extract(request, ctx) }
|
||||
|
||||
// 将提取后的上下文写入当前线程的上下文持有者,供后续组件访问
|
||||
ctx.forEach(StringContextHolder::put)
|
||||
ctx.forEach(contextHolder::put)
|
||||
|
||||
// 调用下一个过滤器或最终的目标处理器
|
||||
chain.forEach { it.inject(request, ctx) }
|
||||
@ -56,7 +57,7 @@ class ContextServletFilter(
|
||||
// 向处理器链注入空上下文,触发清理操作(如有)
|
||||
chain.forEach { it.inject(Unit, mutableMapOf()) }
|
||||
// 显式清除当前线程的上下文持有者,防止上下文泄露
|
||||
StringContextHolder.clear()
|
||||
contextHolder.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
package com.gewuyou.forgeboot.context.impl.filter
|
||||
|
||||
import com.gewuyou.forgeboot.context.api.ContextProcessor
|
||||
import com.gewuyou.forgeboot.context.impl.StringContextHolder
|
||||
import com.gewuyou.forgeboot.context.impl.ContextHolder
|
||||
import com.gewuyou.forgeboot.context.impl.processor.ReactorProcessor
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import org.springframework.web.server.WebFilter
|
||||
@ -22,7 +22,8 @@ import reactor.core.publisher.Mono
|
||||
*/
|
||||
class ContextWebFilter(
|
||||
private val contextProcessors: List<ContextProcessor>,
|
||||
private val reactorProc: ReactorProcessor
|
||||
private val reactorProc: ReactorProcessor,
|
||||
private val contextHolder: ContextHolder
|
||||
) : WebFilter {
|
||||
/**
|
||||
* 执行过滤逻辑,在请求链中插入上下文管理操作。
|
||||
@ -42,12 +43,12 @@ class ContextWebFilter(
|
||||
exchange: ServerWebExchange,
|
||||
chain: WebFilterChain,
|
||||
): Mono<Void> {
|
||||
// 从 StringContextHolder 快照获取当前上下文并转换为可变 Map
|
||||
val ctx = StringContextHolder.snapshot().toMutableMap()
|
||||
// 从 ContextHolder 快照获取当前上下文并转换为可变 Map
|
||||
val ctx = contextHolder.snapshot().toMutableMap()
|
||||
// 遍历所有 ContextProcessor,从请求中提取上下文信息到 ctx
|
||||
contextProcessors.forEach { it.extract(exchange, ctx) }
|
||||
// 将上下文写入 StringContextHolder(ThreadLocal)
|
||||
ctx.forEach(StringContextHolder::put)
|
||||
// 将上下文写入 ContextHolder(ThreadLocal)
|
||||
ctx.forEach(contextHolder::put)
|
||||
// 使用 MdcProcessor 将上下文注入到 MDC 中
|
||||
contextProcessors.forEach { it.inject(Unit, ctx) }
|
||||
// 构建新的 ServerWebExchange 实例
|
||||
@ -60,7 +61,7 @@ class ContextWebFilter(
|
||||
.doFinally {
|
||||
// 清理 ThreadLocal + MDC 上下文
|
||||
contextProcessors.forEach { it.inject(Unit, mutableMapOf()) }
|
||||
StringContextHolder.clear()
|
||||
contextHolder.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user