泛型类与单例状态机协同实现租户隔离:泛型类(如chargepolicy)确保策略类型编译期隔离,状态机通过concurrenthashmap按tenant_id隔离运行时状态,配合不可变对象、深拷贝快照和自动清理机制,杜绝跨租户污染。

核心思路很直接:泛型类负责类型安全的策略封装,单例状态机负责租户上下文的生命周期管控。两者结合,不是简单叠加,而是让“策略类型”和“租户状态”在编译期与运行期各守其界——类型不混、状态不串。
泛型类做策略容器,物理隔离变量类型
计费策略中,按Token、按调用次数、套餐制本质是三类不同结构的数据:前者需记录输入/输出token数,中间者只需计数器,后者要绑定有效期与额度池。若全用Map
用泛型类明确划清边界:
- 定义
ChargePolicy<t></t>抽象基类,含T calculate(UsageRecord record)和void apply(T result) - 实现
TokenPolicy extends ChargePolicy<tokencost></tokencost>、CountPolicy extends ChargePolicy<long></long>、PackagePolicy extends ChargePolicy<packagequota></packagequota> - 每个租户加载时,根据配置决定实例化哪一类,并存入其专属策略槽位(非全局静态池)
这样,A租户的 TokenCost 和B租户的 Long 在JVM里就是完全不同的类型实例,内存地址、反射Class对象、序列化字节流均无交集。
单例状态机管租户上下文,切断跨租户污染链
泛型解决了“类型不混”,但若所有租户共用一个状态机实例,且状态字段是静态或共享缓存,那A租户刚算完的token余额可能被B租户下一次请求意外读取——这是典型的上下文泄漏。
正确做法是:单例状态机只提供“状态路由能力”,不持有具体租户数据。
- 状态机本身是单例(如
TenantStateMachine.INSTANCE),但内部用ConcurrentHashMap<string tenantstate></string>按 tenant_id 隔离状态快照 -
TenantState是私有内部类,含ChargePolicy> currentPolicy、AtomicLong counter、TokenCost balance等强类型字段 - 每次请求进入时,通过
TenantContext.getCurrentTenant()获取ID,状态机自动定位对应TenantState实例,后续所有策略执行、计数更新、余额扣减都在该实例内封闭完成
关键点在于:状态机不暴露 TenantState 引用给外部,所有操作通过 state.apply(record) 封装方法进行,避免外部直接修改字段。
联合实战中的关键防护点
光有结构不够,几个易被忽略的细节决定是否真隔离:
-
策略对象不可变:如
TokenCost所有字段设为 final,构造后禁止修改;变更时返回新实例,防止多线程或跨租户引用污染 -
状态快照深拷贝:当需要跨线程传递(如异步计费日志写入),
TenantState提供snapshot()方法,对策略配置、余额、计数器等做浅拷贝+关键对象深拷贝(如TokenCost的副本) -
租户退出自动清理:在状态机中注册租户注销钩子,移除
ConcurrentHashMap中对应项,并显式置空ChargePolicy引用,防止内存泄漏 -
泛型擦除规避:避免在运行时依赖
policy.getClass().getTypeParameters()判断类型——JVM已擦除。改用策略配置元数据(如数据库中policy_type = 'token')驱动泛型实例化
这种组合不靠语言黑魔法,而靠分层契约:泛型守住编译边界,状态机守住运行边界,租户ID作为唯一锚点贯穿全程。只要租户识别准确,后续所有策略执行、变量读写、状态流转,天然就落在各自物理隔离的“舱室”里。











