refactor(context): PaymentContext脱离Spring Scope, 改为纯ThreadLocal+门面

删除PaymentScope(自定义Scope)和PaymentScopeConfig(BeanFactoryPostProcessor); 新建PaymentContextHolder纯ThreadLocal管理上下文数据(bind/unbind/current); PaymentContext从@Scope CGLIB代理改为@Component单例门面, 方法代理到Holder, 17个注入点(CustomTenantLineHandler/MchTenantMetaObjectHandler/管理端Service等)零改动透明兼容; PaymentScopeManager的start/end改为Holder bind/unbind。
This commit is contained in:
DaxPay Dev
2026-06-28 21:54:22 +08:00
parent 41f854681d
commit d63cab92dc
5 changed files with 69 additions and 129 deletions

View File

@@ -1,28 +1,21 @@
package cn.daxpay.open.payment.common.context;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
/// # 统一支付上下文
/// # 统一支付上下文(门面)
///
/// 合并原 PaymentContextLocal/MchContextLocal 两个ThreadLocal的信息
/// 使用自定义作用域 "payment", 底层基于ThreadLocal, 兼容HTTP/定时任务/MQ场景
/// 保持 @Component 单例,对 17 个注入点透明。实际数据由 [PaymentContextHolder] 通过 ThreadLocal 管理。
/// MyBatis 拦截器(租户隔离、自动填充)通过此门面获取当前线程的商户/应用信息。
@Component
@Scope(value = PaymentScope.SCOPE_NAME, proxyMode = ScopedProxyMode.TARGET_CLASS)
@Getter
@Setter
@Accessors(chain = true)
public class PaymentContext {
// ===== 子对象 =====
/// 交易信息(商户/应用/合作方)
private final TradeInfo tradeInfo = new TradeInfo();
public TradeInfo getTradeInfo() {
return PaymentContextHolder.getTradeInfo();
}
/// 回调相关信息
private final CallbackInfo callbackInfo = new CallbackInfo();
public CallbackInfo getCallbackInfo() {
return PaymentContextHolder.getCallbackInfo();
}
}

View File

@@ -0,0 +1,56 @@
package cn.daxpay.open.payment.common.context;
/// # 支付上下文 ThreadLocal 持有者
///
/// 纯 ThreadLocal 实现,替代原 Spring 自定义 ScopePaymentScope
/// 由 Filter/Manager 在请求入口 bind出口 unbindMyBatis 拦截器通过 PaymentContext 门面间接访问。
public final class PaymentContextHolder {
private PaymentContextHolder() {
}
private static final ThreadLocal<ContextData> HOLDER = new ThreadLocal<>();
/// 绑定新上下文到当前线程
public static void bind() {
if (HOLDER.get() != null) {
throw new IllegalStateException("PaymentContext already active on this thread");
}
HOLDER.set(new ContextData());
}
/// 解除当前线程的上下文绑定
public static void unbind() {
HOLDER.remove();
}
/// 当前线程是否已绑定上下文
public static boolean isBound() {
return HOLDER.get() != null;
}
/// 获取当前线程的交易信息
public static TradeInfo getTradeInfo() {
return current().tradeInfo;
}
/// 获取当前线程的回调信息
public static CallbackInfo getCallbackInfo() {
return current().callbackInfo;
}
/// 获取当前上下文数据(未绑定则抛异常)
private static ContextData current() {
ContextData data = HOLDER.get();
if (data == null) {
throw new IllegalStateException("PaymentContext not active on this thread");
}
return data;
}
/// 每次请求的上下文数据
private static class ContextData {
final TradeInfo tradeInfo = new TradeInfo();
final CallbackInfo callbackInfo = new CallbackInfo();
}
}

View File

@@ -1,86 +0,0 @@
package cn.daxpay.open.payment.common.context;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import java.util.concurrent.ConcurrentHashMap;
/// # 支付自定义作用域, 底层基于ThreadLocal实现
///
/// 兼容HTTP请求、定时任务、MQ消费者等场景
public class PaymentScope implements Scope, DisposableBean {
public static final String SCOPE_NAME = "payment";
private final ThreadLocal<PaymentContext> threadLocal = new ThreadLocal<>();
private ObjectFactory<?> objectFactory;
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if (this.objectFactory == null) {
this.objectFactory = objectFactory;
}
PaymentContext context = threadLocal.get();
if (context == null) {
context = (PaymentContext) objectFactory.getObject();
threadLocal.set(context);
}
return context;
}
@Override
public Object remove(String name) {
PaymentContext context = threadLocal.get();
threadLocal.remove();
return context;
}
/// 开启作用域, 创建PaymentContext并绑定到当前线程
public void start() {
if (threadLocal.get() != null) {
throw new IllegalStateException("PaymentScope already active on this thread");
}
if (objectFactory != null) {
get(SCOPE_NAME, objectFactory);
} else {
threadLocal.set(new PaymentContext());
}
}
/// 结束作用域, 清除当前线程的PaymentContext
public void end() {
threadLocal.remove();
}
/// 获取当前上下文
/// 用于非Spring管理的类或需要静态访问的场景
public PaymentContext current() {
PaymentContext context = threadLocal.get();
if (context == null) {
throw new IllegalStateException("PaymentScope not active on this thread");
}
return context;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
@Override
public void destroy() {
threadLocal.remove();
}
}

View File

@@ -1,22 +0,0 @@
package cn.daxpay.open.payment.common.context;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/// # 支付作用域注册配置
///
@Configuration
public class PaymentScopeConfig implements BeanFactoryPostProcessor {
@Bean
public PaymentScope paymentScope() {
return new PaymentScope();
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.registerScope(PaymentScope.SCOPE_NAME, beanFactory.getBean(PaymentScope.class));
}
}

View File

@@ -1,5 +1,6 @@
package cn.daxpay.open.payment.common.context;
import cn.daxpay.open.payment.common.context.PaymentContextHolder;
import cn.daxpay.open.payment.old.pay.service.assist.PaymentAssistService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@@ -13,25 +14,23 @@ import java.util.function.Supplier;
@RequiredArgsConstructor
public class PaymentScopeManager {
private final PaymentScope paymentScope;
private final PaymentAssistService paymentAssistService;
/// 开启作用域(空上下文)
public void start() {
paymentScope.start();
PaymentContextHolder.bind();
}
/// 开启作用域并初始化商户信息
/// 用于定时任务/MQ消费者等非HTTP场景
public void startWithMch(String mchNo, String appId) {
paymentScope.start();
PaymentContextHolder.bind();
paymentAssistService.initMchAndApp(mchNo, appId);
}
/// 结束作用域
public void end() {
paymentScope.end();
PaymentContextHolder.unbind();
}
/// 在作用域内执行(自动管理生命周期, 同步执行)