feat(context): Core functions of context propagation

- Added FieldDef class to define field properties
- Create Scope enumeration class, define the context storage location
- Implement AbstractContext and Context interfaces
- Add the ContextFieldContributor interface and the FieldRegistry interface
- Implement context processors: HeaderProcessor, GeneratorProcessor, MdcProcessor, ReactorProcessor
- Add context filters: ContextServletFilter, ContextWebFilter
- Implement the automatic configuration class ForgeContextAutoConfiguration
- Add StringContextHolder
- Implement the default field registry DefaultFieldRegistry
This commit is contained in:
gewuyou 2025-06-05 16:06:44 +08:00
parent 446b859a8a
commit ebe801f6bb
29 changed files with 1112 additions and 0 deletions

3
forgeboot-context/.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary

40
forgeboot-context/.gitignore vendored Normal file
View File

@ -0,0 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Kotlin ###
.kotlin

View File

@ -0,0 +1,8 @@
extra {
// 标记为根项目
setProperty(ProjectFlags.IS_ROOT_MODULE, true)
}
dependencies {
}

View File

@ -0,0 +1,3 @@
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary

View File

@ -0,0 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Kotlin ###
.kotlin

View File

@ -0,0 +1,4 @@
dependencies {
}

View File

@ -0,0 +1,57 @@
package com.gewuyou.forgeboot.context.api
/**
*抽象上下文
*
* @since 2025-06-04 13:36:54
* @author gewuyou
*/
abstract class AbstractContext<K, V>: Context<K, V> {
private val local= ThreadLocal.withInitial { mutableMapOf<K,V>() }
/**
* 将指定的键值对存入上下文中
*
* @param key 要存储的键
* @param value 要存储的值可以为 null
*/
override fun put(key: K, value: V?) {
value?.let {
local.get()[key] = it
}
}
/**
* 根据指定的键从上下文中获取对应的值
*
* @param key 要查找的键
* @return 对应的值如果不存在则返回 null
*/
override fun get(key: K): V? {
return local.get()[key]
}
/**
* 获取当前上下文的一个快照包含所有键值对
*
* @return 一个 Map表示当前上下文中的所有键值对
*/
override fun snapshot(): Map<K, V> {
return HashMap(local.get())
}
/**
* 清除上下文中的所有键值对
*/
override fun clear() {
local.remove()
}
/**
* 从上下文中移除指定的键值对
*
* @param key 要移除的键
*/
override fun remove(key: K) {
local.get().remove(key)
}
}

View File

@ -0,0 +1,44 @@
package com.gewuyou.forgeboot.context.api
/**
* 上下文接口用于管理键值对的存储获取和清理操作
*
* @since 2025-06-04 13:34:04
* @author gewuyou
*/
interface Context<K, V> {
/**
* 将指定的键值对存入上下文中
*
* @param key 要存储的键
* @param value 要存储的值可以为 null
*/
fun put(key: K, value: V?)
/**
* 根据指定的键从上下文中获取对应的值
*
* @param key 要查找的键
* @return 对应的值如果不存在则返回 null
*/
fun get(key: K): V?
/**
* 获取当前上下文的一个快照包含所有键值对
*
* @return 一个 Map表示当前上下文中的所有键值对
*/
fun snapshot(): Map<K, V?>
/**
* 清除上下文中的所有键值对
*/
fun clear()
/**
* 从上下文中移除指定的键值对
*
* @param key 要移除的键
*/
fun remove(key: K)
}

View File

@ -0,0 +1,20 @@
package com.gewuyou.forgeboot.context.api
import com.gewuyou.forgeboot.context.api.entities.FieldDef
/**
* 上下文字段贡献者接口用于定义上下文所需字段的契约
*
* 实现此接口的类应提供一组上下文字段定义[FieldDef]用于描述当前上下文的数据结构
*
* @since 2025-06-04 13:32:39
* @author gewuyou
*/
fun interface ContextFieldContributor {
/**
* 提供上下文字段定义集合
*
* @return 返回一个不可变的[Set]集合包含当前上下文所需的所有字段定义对象[FieldDef]
*/
fun fields(): Set<FieldDef>
}

View File

@ -0,0 +1,53 @@
package com.gewuyou.forgeboot.context.api
/**
* 上下文处理器
*
* 用于定义上下文的处理逻辑包括顺序控制上下文提取与注入
*
* @since 2025-06-04 15:07:13
* @author gewuyou
*/
interface ContextProcessor : Comparable<ContextProcessor> {
/**
* 获取当前处理器的执行顺序优先级
*
* 默认实现返回0数值越小优先级越高
*
* @return Int 表示当前处理器的顺序值
*/
fun order(): Int = 0
/**
* 从给定的载体中提取上下文信息并填充到上下文对象中
*
* 默认实现为空方法子类可根据需要重写此方法
*
* @param carrier 载体对象通常包含上下文数据
* @param ctx 可变映射用于存储提取出的上下文键值对
*/
fun extract(carrier: Any, ctx: MutableMap<String, String>) {
// do nothing
}
/**
* 将上下文信息注入到给定的载体中
*
* 默认实现为空方法子类可根据需要重写此方法
*
* @param carrier 载体对象将上下文数据注入其中
* @param ctx 包含上下文键值对的映射
*/
fun inject(carrier: Any, ctx: MutableMap<String, String>) {
// do nothing
}
/**
* 根据处理器的执行顺序进行比较实现Comparable接口的方法
*
* @param other 待比较的另一个上下文处理器
* @return Int 当前对象与other对象的顺序差值用于排序
*/
override fun compareTo(other: ContextProcessor) = order() - other.order()
}

View File

@ -0,0 +1,29 @@
package com.gewuyou.forgeboot.context.api
import com.gewuyou.forgeboot.context.api.entities.FieldDef
/**
* 字段注册表接口
*
* 该接口定义了字段注册表的基本操作包括获取所有字段定义和通过字段头查询字段定义
*
* @since 2025-06-04 14:44:40
* @author gewuyou
*/
interface FieldRegistry {
/**
* 获取所有字段定义的集合
*
* @return 返回包含所有字段定义的集合
*/
fun all(): Collection<FieldDef>
/**
* 根据字段头查找对应的字段定义
*
* @param header 字段头信息
* @return 如果找到匹配的字段定义则返回否则返回 null
*/
fun findByHeader(header: String): FieldDef?
}

View File

@ -0,0 +1,21 @@
package com.gewuyou.forgeboot.context.api.entities
import com.gewuyou.forgeboot.context.api.enums.Scope
/**
* 字段定义用于描述数据结构中的字段属性
*
* @property header 字段的显示名称通常用于界面展示
* @property key 字段的唯一标识符用于数据映射和识别
* @property generator 生成字段值的函数默认为 null表示不使用动态生成
* @property scopes 定义该字段适用的上下文范围默认包括 HEADER MDC
*
* @since 2025-06-04 13:31:32
* @author gewuyou
*/
data class FieldDef(
val header: String,
val key: String,
val generator: (() -> String)? = null,
val scopes: Set<Scope> = setOf(Scope.HEADER, Scope.MDC)
)

View File

@ -0,0 +1,29 @@
package com.gewuyou.forgeboot.context.api.enums
/**
* 范围枚举类定义了不同上下文信息的存储位置
*
* @since 2025-06-04 13:29:57
* @author gewuyou
*/
enum class Scope {
/**
* 存储在请求头Header
*/
HEADER,
/**
* 存储在 MDCMapped Diagnostic Context用于日志跟踪
*/
MDC,
/**
* 存储在 Reactor 上下文中
*/
REACTOR,
/**
* 存储在 Baggage 用于分布式追踪
*/
BAGGAGE
}

View File

@ -0,0 +1,3 @@
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary

View File

@ -0,0 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Kotlin ###
.kotlin

View File

@ -0,0 +1,13 @@
plugins{
alias(libs.plugins.kotlin.plugin.spring)
}
dependencies {
implementation(platform(libs.springBootDependencies.bom))
implementation(libs.springBoot.autoconfigure)
compileOnly(project(Modules.Context.API))
compileOnly(project(Modules.Context.IMPL))
compileOnly(libs.springCloudDependencies.bom)
compileOnly(libs.springBootStarter.web)
compileOnly(libs.springBootStarter.webflux)
compileOnly(libs.springCloudStarter.openfeign)
}

View File

@ -0,0 +1,253 @@
package com.gewuyou.forgeboot.context.autoconfigure
import com.gewuyou.forgeboot.context.api.*
import com.gewuyou.forgeboot.context.impl.*
import com.gewuyou.forgeboot.context.impl.filter.ContextServletFilter
import com.gewuyou.forgeboot.context.impl.filter.ContextWebFilter
import com.gewuyou.forgeboot.context.impl.processor.*
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
/**
* 配置类用于自动配置上下文相关的 Bean
*
* 该配置类根据不同的运行时依赖和配置条件定义了一系列的 Bean
* 实现了上下文字段在不同场景下的传播与管理机制
*
* @since 2025-06-04 11:48:01
* @author gewuyou
*/
@Configuration
class ForgeContextAutoConfiguration {
/*
0 通用 Bean不依赖 Web / Feign / Reactor 等外部包
*/
/**
* 创建 FieldRegistry Bean用于注册上下文中所有字段定义
*
* FieldRegistry 是上下文字段的核心注册中心聚合所有 ContextFieldContributor 提供的字段定义
*
* @param contributors 提供字段定义的贡献者列表
* @return 构建完成的 FieldRegistry 实例
*/
@Bean
@ConditionalOnMissingBean
fun fieldRegistry(contributors: List<ContextFieldContributor>): FieldRegistry =
DefaultFieldRegistry(contributors.flatMap { it.fields() }.toSet())
/**
* 创建 HeaderProcessor Bean用于处理请求头中的上下文字段
*
* HeaderProcessor 负责从请求头中提取上下文字段并注入到当前线程上下文中
*
* @param reg 字段注册表
* @return 构建完成的 HeaderProcessor 实例
*/
@Bean("headerProcessor")
fun headerProcessor(reg: FieldRegistry) = HeaderProcessor(reg)
/**
* 创建 GeneratorProcessor Bean用于生成上下文字段值
*
* GeneratorProcessor 根据字段定义生成默认值 traceIdspanId 适用于首次进入系统的情况
*
* @param reg 字段注册表
* @return 构建完成的 GeneratorProcessor 实例
*/
@Bean("generatorProcessor")
fun generatorProcessor(reg: FieldRegistry) = GeneratorProcessor(reg)
/**
* 创建 MdcProcessor Bean用于将上下文字段写入 MDCMapped Diagnostic Context
*
* MdcProcessor 使得日志框架 Logback可以访问当前上下文字段便于日志追踪
*
* @param reg 字段注册表
* @return 构建完成的 MdcProcessor 实例
*/
@Bean("mdcProcessor")
fun mdcProcessor(reg: FieldRegistry) = MdcProcessor(reg)
/*
1 Reactor 支持只有 classpath Reactor 时才激活
*/
/**
* 配置类提供对 Reactor 上下文传播的支持
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = ["reactor.util.context.Context"])
class ReactorSupport {
/**
* 创建 ReactorProcessor Bean用于在 Reactor 上下文中传播上下文字段
*
* ReactorProcessor 适配了 Reactor Context 接口确保上下文字段在响应式流中正确传递
*
* @param reg 字段注册表
* @return 构建完成的 ReactorProcessor 实例
*/
@Bean("reactorProcessor")
fun reactorProcessor(reg: FieldRegistry) = ReactorProcessor(reg)
}
/*
2 WebFlux 过滤器依赖 WebFlux + Reactor
*/
/**
* 配置类注册 WebFlux 环境下的上下文传播过滤器
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = ["spring.main.web-application-type"], havingValue = "reactive")
@ConditionalOnClass(name = ["org.springframework.web.server.WebFilter"])
class WebFluxPart {
/**
* 注册 ContextWebFilter Bean用于在 WebFlux 请求链中传播上下文字段
*
* 该过滤器利用 ReactorProcessor WebFlux 的过滤器链中维护上下文一致性
*
* @param chain 处理器链
* @param reactorProcessor ReactorProcessor 实例
* @return 构建完成的 ContextWebFilter 实例
*/
@Bean
@ConditionalOnMissingBean
fun contextWebFilter(
chain: List<ContextProcessor>,
reactorProcessor: ReactorProcessor,
) = ContextWebFilter(chain, reactorProcessor)
}
/*
3 Servlet 过滤器依赖 Servlet API
*/
/**
* 配置类注册 Servlet 环境下的上下文传播过滤器
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = ["spring.main.web-application-type"], havingValue = "servlet", matchIfMissing = true)
@ConditionalOnClass(name = ["jakarta.servlet.Filter"])
class ServletPart {
/**
* 注册 ContextServletFilter Bean用于在 Servlet 请求链中传播上下文字段
*
* 该过滤器负责在同步阻塞的 Servlet 请求链中维护上下文一致性
*
* @param chain 处理器链
* @return 构建完成的 ContextServletFilter 实例
*/
@Bean
@ConditionalOnMissingBean
fun contextServletFilter(chain: List<ContextProcessor>) =
ContextServletFilter(chain)
}
/*
4 Feign 请求拦截器依赖 OpenFeign
*/
/**
* 配置类注册 Feign 客户端的请求拦截器
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = ["feign.RequestInterceptor"])
class FeignPart {
/**
* 注册 Feign 请求拦截器用于在 Feign 调用中传播上下文字段
*
* 拦截器会在每次 Feign 请求发起前将当前上下文字段写入 HTTP 请求头
*
* @param registry 字段注册表
* @return 构建完成的 feign.RequestInterceptor 实例
*/
@Bean
@ConditionalOnMissingBean
fun feignInterceptor(registry: FieldRegistry) =
feign.RequestInterceptor { tpl ->
val ctx = StringContextHolder.snapshot()
registry.all().forEach { def ->
ctx[def.key]?.let { tpl.header(def.header, it) }
}
}
}
/*
5 线程池 TaskDecorator Spring安全通用
*/
/**
* 配置类注册异步执行上下文保持支持
*/
@Configuration(proxyBeanMethods = false)
class TaskDecoratorPart {
/**
* 创建 TaskDecorator Bean用于在异步执行中保持上下文一致性
*
* 通过装饰线程池任务确保异步任务继承调用线程的上下文状态
*
* @param processors 所有处理器列表
* @return 构建完成的 TaskDecorator 实例
*/
@Bean
fun contextTaskDecorator(processors: List<ContextProcessor>) =
TaskDecorator { delegate ->
val snap = StringContextHolder.snapshot()
Runnable {
try {
snap.forEach(StringContextHolder::put)
processors.forEach { it.inject(Unit, snap.toMutableMap()) }
delegate.run()
} finally {
processors.forEach { it.inject(Unit, mutableMapOf()) }
StringContextHolder.clear()
}
}
}
}
/*
6 WebClient 过滤器
*/
/**
* 配置类注册 WebClient 自定义器以支持上下文传播
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = ["org.springframework.web.reactive.function.client.WebClient"])
class WebClientPart(private val registry: FieldRegistry) {
/**
* 注册 WebClientCustomizer用于定制 WebClient 的请求行为
*
* 在每次请求发出前将当前上下文字段写入 HTTP 请求头
*
* @return 构建完成的 WebClientCustomizer 实例
*/
@Bean
fun contextWebClientCustomizer() = WebClientCustomizer { builder ->
builder.filter { req, next ->
val ctx = StringContextHolder.snapshot()
val mutated = ClientRequest.from(req).apply {
registry.all().forEach { def ->
ctx[def.key]?.let { value -> header(def.header, value) }
}
}.build()
next.exchange(mutated)
}
}
}
}

View File

@ -0,0 +1 @@
com.gewuyou.forgeboot.context.autoconfigure.ForgeContextAutoConfiguration

View File

@ -0,0 +1,3 @@
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary

View File

@ -0,0 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Kotlin ###
.kotlin

View File

@ -0,0 +1,8 @@
dependencies {
compileOnly(project(Modules.Context.API))
compileOnly(libs.springBootStarter.web)
compileOnly(libs.springBootStarter.webflux)
implementation(platform(libs.springBootDependencies.bom))
implementation(libs.springBoot.autoconfigure)
}

View File

@ -0,0 +1,45 @@
package com.gewuyou.forgeboot.context.impl
import com.gewuyou.forgeboot.context.api.FieldRegistry
import com.gewuyou.forgeboot.context.api.entities.FieldDef
/**
* 默认字段注册表
*
* 该类实现了字段的注册和查找功能通过字段的 key header 建立索引
* 提供了基于 header 的快速查找能力
*
* @param defs 初始化字段定义集合用于构建注册表
* @since 2025-06-04 14:51:39
* @author gewuyou
*/
class DefaultFieldRegistry(defs: Set<FieldDef>) : FieldRegistry {
/**
* 按字段 key 构建的有序映射表用于通过 key 快速访问字段定义
* 保持字段注册顺序的一致性适用于需要按注册顺序遍历的场景
*/
private val byKey: Map<String, FieldDef> =
LinkedHashMap<String, FieldDef>().apply {
defs.forEach { put(it.key, it) }
}
/**
* 按字段 header小写形式构建的映射表用于通过 header 快速查找字段定义
*/
private val byHeader = defs.associateBy { it.header.lowercase() }
/**
* 获取注册表中所有的字段定义
*
* @return 字段定义的集合
*/
override fun all() = byKey.values
/**
* 根据字段 header 查找对应的字段定义
*
* @param header 要查找的字段 header不区分大小写
* @return 匹配到的字段定义若未找到则返回 null
*/
override fun findByHeader(header: String) = byHeader[header.lowercase()]
}

View File

@ -0,0 +1,11 @@
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>()

View File

@ -0,0 +1,62 @@
package com.gewuyou.forgeboot.context.impl.filter
import com.gewuyou.forgeboot.context.api.ContextProcessor
import com.gewuyou.forgeboot.context.impl.StringContextHolder
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.web.filter.OncePerRequestFilter
/**
* 上下文 Servlet 过滤器
*
* 该过滤器用于在 HTTP 请求处理过程中提取和注入上下文信息
* 确保请求链中各组件可以共享上下文数据
*
* @property chain 处理上下文的处理器链表按顺序依次执行
*
* @since 2025-06-04 16:08:33
* @author gewuyou
*/
class ContextServletFilter(
private val chain: List<ContextProcessor>,
) : OncePerRequestFilter() {
/**
* 执行内部过滤逻辑
*
* 在请求进入业务逻辑前从请求中提取上下文信息并存储到上下文持有者中
* 在请求完成后清理上下文以避免内存泄漏或上下文污染
*
* @param request 当前 HTTP 请求对象
* @param response 当前 HTTP 响应对象
* @param filterChain 过滤器链用于继续执行后续过滤器或目标处理逻辑
*/
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain,
) {
// 创建当前线程上下文快照的可变副本,确保后续操作不影响原始上下文
val ctx = StringContextHolder.snapshot().toMutableMap()
// 遍历上下文处理器链,依次从请求中提取上下文信息并更新临时上下文容器
chain.forEach { it.extract(request, ctx) }
// 将提取后的上下文写入当前线程的上下文持有者,供后续组件访问
ctx.forEach(StringContextHolder::put)
// 调用下一个过滤器或最终的目标处理器
chain.forEach { it.inject(request, ctx) }
try {
filterChain.doFilter(request, response)
} finally {
// 确保在请求结束时清理所有上下文资源
// 向处理器链注入空上下文,触发清理操作(如有)
chain.forEach { it.inject(Unit, mutableMapOf()) }
// 显式清除当前线程的上下文持有者,防止上下文泄露
StringContextHolder.clear()
}
}
}

View File

@ -0,0 +1,66 @@
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.processor.ReactorProcessor
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
import org.springframework.web.server.WebFilterChain
import reactor.core.publisher.Mono
/**
* 上下文 Web 过滤器
*
* 用于在 Web 请求处理过程中维护和传递上下文信息包括 ThreadLocalMDC Reactor Context
* 通过一系列的 [ContextProcessor] 实现对上下文的提取注入和清理操作
*
* @property contextProcessors 上下文处理器列表用于处理上下文的提取与注入逻辑
* @property reactorProc 反应式上下文处理器用于将上下文写入 Reactor Context
*
* @since 2025-06-04 15:54:43
* @author gewuyou
*/
class ContextWebFilter(
private val contextProcessors: List<ContextProcessor>,
private val reactorProc: ReactorProcessor
) : WebFilter {
/**
* 执行过滤逻辑在请求链中插入上下文管理操作
*
* 此方法依次完成以下步骤
* 1. 提取当前上下文并构建新的上下文数据
* 2. 将上下文填充到 ThreadLocal MDC
* 3. 将上下文注入到请求头中以构建新地请求
* 4. 继续执行请求链并将上下文写入 Reactor Context
* 5. 最终清理 ThreadLocalMDC 中的上下文
*
* @param exchange 当前的 ServerWebExchange 对象代表 HTTP 请求交换
* @param chain Web 过滤器链用于继续执行后续的过滤器或目标处理逻辑
* @return 返回一个 Mono<Void> 表示异步完成的操作
*/
override fun filter(
exchange: ServerWebExchange,
chain: WebFilterChain,
): Mono<Void> {
// 从 StringContextHolder 快照获取当前上下文并转换为可变 Map
val ctx = StringContextHolder.snapshot().toMutableMap()
// 遍历所有 ContextProcessor从请求中提取上下文信息到 ctx
contextProcessors.forEach { it.extract(exchange, ctx) }
// 将上下文写入 StringContextHolderThreadLocal
ctx.forEach(StringContextHolder::put)
// 使用 MdcProcessor 将上下文注入到 MDC 中
contextProcessors.forEach { it.inject(Unit, ctx) }
// 构建新的 ServerWebExchange 实例
val mutated = exchange.mutate()
// 注入上下文到请求头中
contextProcessors.forEach { it.inject(mutated, ctx) }
// 继续执行过滤器链,同时将上下文写入 Reactor Context
return chain.filter(mutated.build())
.contextWrite(reactorProc.injectToReactor(ctx))
.doFinally {
// 清理 ThreadLocal + MDC 上下文
contextProcessors.forEach { it.inject(Unit, mutableMapOf()) }
StringContextHolder.clear()
}
}
}

View File

@ -0,0 +1,47 @@
package com.gewuyou.forgeboot.context.impl.processor
import com.gewuyou.forgeboot.context.api.ContextProcessor
import com.gewuyou.forgeboot.context.api.FieldRegistry
/**
* 生成器处理器
*
* 用于通过字段注册表FieldRegistry动态填充上下文中的空白字段
* 当前类实现了 ContextProcessor 接口提供了一种自动化的上下文字段填充机制
*
* @since 2025-06-04 15:35:11
* @author gewuyou
*/
class GeneratorProcessor(
private val reg: FieldRegistry
) : ContextProcessor {
/**
* 获取当前处理器的执行顺序优先级
*
* 在多个 ContextProcessor 实现中该方法决定本处理器的执行顺序
* 数值越小优先级越高在上下文处理流程中就越早被调用
*
* @return Int 表示当前处理器的顺序值默认为20
*/
override fun order(): Int {
return 20
}
/**
* 从给定的载体中提取上下文信息并填充到上下文对象中
*
* 遍历 FieldRegistry 中注册的所有字段定义
* - 如果当前字段在上下文中不存在或为空白则尝试使用其关联的生成器函数进行填充
* - 生成器函数非空时会被调用并将结果存入上下文映射中
*
* @param carrier 载体对象通常包含上下文数据未使用于当前实现
* @param ctx 可变映射用于存储提取出的上下文键值对
*/
override fun extract(carrier: Any, ctx: MutableMap<String, String>) {
reg.all().forEach { def ->
if (ctx[def.key].isNullOrBlank()) {
def.generator?.invoke()?.let { ctx[def.key] = it }
}
}
}
}

View File

@ -0,0 +1,72 @@
package com.gewuyou.forgeboot.context.impl.processor
import com.gewuyou.forgeboot.context.api.ContextProcessor
import com.gewuyou.forgeboot.context.api.FieldRegistry
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.web.server.ServerWebExchange
/**
* 请求头处理器
*
* @since 2025-06-04 15:14:57
* @author gewuyou
*/
class HeaderProcessor(private val reg: FieldRegistry) : ContextProcessor {
/**
* 获取当前处理器的执行顺序优先级
*
* 默认实现返回0数值越小优先级越高
*
* @return Int 表示当前处理器的顺序值
*/
override fun order(): Int {
return 10
}
/**
* 从给定的载体中提取上下文信息并填充到上下文对象中
*
* 默认实现为空方法子类可根据需要重写此方法
*
* @param carrier 载体对象通常包含上下文数据
* @param ctx 可变映射用于存储提取出的上下文键值对
*/
override fun extract(carrier: Any, ctx: MutableMap<String, String>) {
when (carrier) {
is ServerWebExchange -> reg.all().forEach { def ->
// 从ServerWebExchange请求头中提取指定字段并存入上下文
carrier.request.headers[def.header]?.firstOrNull()?.let { ctx[def.key] = it }
}
is HttpServletRequest -> reg.all().forEach { def ->
// 从HttpServletRequest请求头中提取指定字段并存入上下文
carrier.getHeader(def.header)?.let { ctx[def.key] = it }
}
}
}
/**
* 将上下文信息注入到给定的载体中
*
* 默认实现为空方法子类可根据需要重写此方法
*
* @param carrier 载体对象将上下文数据注入其中
* @param ctx 包含上下文键值对的映射
*/
override fun inject(carrier: Any, ctx: MutableMap<String, String>) {
when (carrier) {
is ServerWebExchange.Builder -> reg.all().forEach { def ->
// 向ServerWebExchange构建器中注入请求头字段
ctx[def.key]?.let { value ->
carrier.request { reqBuilder ->
reqBuilder.header(def.header, value)
}
}
}
is HttpServletResponse -> reg.all().forEach { def ->
// 向HttpServletResponse中设置对应的响应头字段
ctx[def.key]?.let { carrier.setHeader(def.header, it) }
}
}
}
}

View File

@ -0,0 +1,48 @@
package com.gewuyou.forgeboot.context.impl.processor
import com.gewuyou.forgeboot.context.api.ContextProcessor
import com.gewuyou.forgeboot.context.api.FieldRegistry
import org.slf4j.MDC
/**
* MDC 处理器
* ctx 写入 SLF4J MDC方便日志打印请求结束或线程任务结束时清理
*
*
* @since 2025-06-04 15:39:35
* @author gewuyou
*/
class MdcProcessor(
private val reg: FieldRegistry,
) : ContextProcessor {
/**
* 获取当前处理器的执行顺序优先级
*
* 默认实现返回0数值越小优先级越高
*
* @return Int 表示当前处理器的顺序值
*/
override fun order(): Int {
return 30
}
/**
* 将上下文信息注入到给定的载体中
*
* 默认实现为空方法子类可根据需要重写此方法
*
* @param carrier 载体对象将上下文数据注入其中
* @param ctx 包含上下文键值对的映射
*/
override fun inject(carrier: Any, ctx: MutableMap<String, String>) {
if (ctx.isEmpty()) {
// 视为空 ctx → 清理
reg.all().forEach { def -> MDC.remove(def.key) }
} else {
// 正常写入
reg.all().forEach { def ->
ctx[def.key]?.let { MDC.put(def.key, it) }
}
}
}
}

View File

@ -0,0 +1,49 @@
package com.gewuyou.forgeboot.context.impl.processor
import com.gewuyou.forgeboot.context.api.ContextProcessor
import com.gewuyou.forgeboot.context.api.FieldRegistry
import reactor.util.context.Context
import java.util.function.Function
/**
* 反应器处理器
*
* 用于在 WebFlux/Reactor 流中透传字段当进入异步链时将字段写入 reactor.util.context.Context
* Context 在链尾无需手动清理会自动复制和管理
*
* @property reg 字段注册表用于获取所有需要处理的字段定义
* @since 2025-06-04 15:43:20
* @author gewuyou
*/
class ReactorProcessor(
private val reg: FieldRegistry
) : ContextProcessor {
/**
* 获取当前处理器的执行顺序优先级
*
* 默认实现返回0数值越小优先级越高
*
* @return Int 表示当前处理器的顺序值
*/
override fun order(): Int {
return 40
}
/**
* 创建一个函数用于将给定的上下文数据注入到 Reactor Context
*
* 遍历字段注册表中的所有字段定义并将对应的值从输入上下文中取出
* 然后放入 Reactor Context
*
* @param ctx 包含要注入字段的映射表key-value
* @return Function<Context, Context> 返回一个函数该函数接受原始的 Reactor Context 并返回更新后的 Context
*/
fun injectToReactor(ctx: Map<String, String>): Function<Context, Context> =
Function { rCtx ->
var updated = rCtx
// 遍历所有字段定义,并将对应的值注入到 Reactor Context 中
reg.all().forEach { def ->
ctx[def.key]?.let { value -> updated = updated.put(def.key, value) }
}
updated
}
}