Spring @async 子线程上下文多租户

Posted

技术标签:

【中文标题】Spring @async 子线程上下文多租户【英文标题】:Spring @async child thread context multitenancy 【发布时间】:2018-06-26 02:42:34 【问题描述】:

我有一个应用程序,它向我的 java 应用程序发送一些 REST 调用。我必须在 @Async 模式下运行最后一个命令,以便用户可以在有异步任务执行另一个 SOAP 调用时继续使用应用程序。

问题是我已经在数据库中自动装配了 TenantContext 和几个租户标识符。在执行异步任务时,它具有我的主线程的租户上下文,并为错误的租户保存数据。

这是我的 JpaTransactionManager,它为数据库中的每个事务调用:

@Autowired
private TenantContext tenantContext;

@Autowired
private Flyway flyway;

@Autowired
private Environment environment;

@Override
protected void doBegin(final Object transaction, final TransactionDefinition definition)

    super.doBegin(transaction, definition);
    final EntityManagerHolder entityManagerHolder = (EntityManagerHolder) TransactionSynchronizationManager.getResource(getEntityManagerFactory());
    final EntityManager entityManager = entityManagerHolder.getEntityManager();

    String subAccountId = "";
    if (environment.getActiveProfiles().length == 0 || !environment.getActiveProfiles()[0].equals("production"))
    
        subAccountId = "SCAN4CLOUD";
     else
    
        subAccountId = tenantContext.getTenant().getAccount().getId().toUpperCase();
    

    entityManager.setProperty("tenant", subAccountId);

我尝试使用以下类拦截异步调用以设置正确的租户上下文。

@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport

@Autowired
private TenantContext tenantContext;

@Autowired
private Environment environment;

private HashMap<String, ContextAwarePoolExecutor> tenantThreadPoolTaskExecutor = new HashMap<String, ContextAwarePoolExecutor>();

@Override
@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public Executor getAsyncExecutor()


    String subAccountId = "";

    if (environment.getActiveProfiles().length == 0 || !environment.getActiveProfiles()[0].equals("production"))
    
        subAccountId = "SCAN4CLOUD";
     else
    
        subAccountId = tenantContext.getTenant().getAccount().getId().toUpperCase();
    

    if (!tenantThreadPoolTaskExecutor.containsKey(subAccountId))
    
        tenantThreadPoolTaskExecutor.put(subAccountId, new ContextAwarePoolExecutor(tenantContext));
    

    return tenantThreadPoolTaskExecutor.get(subAccountId);

ContextAwarePoolExecutor:

public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor

    private TenantContext tenantContext;

    public ContextAwarePoolExecutor(TenantContext tenantContext)
    
        this.tenantContext = tenantContext;
    

    @Override
    public <T> Future<T> submit(Callable<T> task)
    
        return super.submit(new ContextAwareCallable(task, tenantContext));
    

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task)
    
        return super.submitListenable(new ContextAwareCallable(task, tenantContext));
    

ContextAwareCallable:

public class ContextAwareCallable<T> implements Callable<T>

private Callable<T> task;
private TenantContext tenantContext;

public ContextAwareCallable(Callable<T> task, TenantContext tenantContext)

    this.task = task;
    this.tenantContext = tenantContext;


@Override
public T call() throws Exception

    if (tenantContext != null)
    
        return tenantContext.execute(tenantContext.getTenant().getId(), task);
    

    return task.call();
    

但它仍然没有给我父线程的正确租户。

对此有何建议或其他方法?

谢谢, 没有Ta

【问题讨论】:

【参考方案1】:

Tenantcontext 类来自 SAP:import com.sap.cloud.account.TenantContext; https://appdoc.app/artifact/com.sap.cloud/neo-java-web-api/1.105.21/com/sap/cloud/account/TenantContext.html

import java.util.concurrent.Callable;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import com.sap.cloud.account.TenantContext;

public class ContextAwareCallable<T> implements Callable<T>

    private Callable<T> task;
    private TenantContext tenantContext;
    private String tenantId;
    private RequestAttributes context;

    public ContextAwareCallable(Callable<T> task, TenantContext tenantContext, RequestAttributes context)
    
        this.task = task;
        this.tenantContext = tenantContext;
        this.context = context;
        if (tenantContext != null)
        
            this.tenantId = tenantContext.getTenant().getId();
        
    

    @Override
    public T call() throws Exception
    
        if (context != null)
        
            RequestContextHolder.setRequestAttributes(context);
        

        if (tenantId != null)
        
            return tenantContext.execute(tenantId, task);
        

        return task.call();
    

【讨论】:

【参考方案2】:

我通过在 contextawarecallable 构造函数中直接添加tenantid 解决了这个问题。现在它正在工作。

【讨论】:

但是你已经在构造函数中传递了tenantContext,它应该已经标识了tenantId。这对您有什么帮助? 我应该发布一些代码解释,因为我也不知道在写这篇文章时我在想什么。我只知道我在访问租户上下文时遇到了一些问题,所以我在 ContextAwareCallable 私有字符串租户 ID 中创建了一个变量,并在构造函数中传递了租户 ID。但我不确定为什么我在那里遇到了一些问题。 您能否发布您所做的更正?你也可以发布 TenantContext 类吗?

以上是关于Spring @async 子线程上下文多租户的主要内容,如果未能解决你的问题,请参考以下文章

C#异步异步多线程的本质,上下文流转和同步

带有子上下文的核心数据多线程

python 异步操作async和await

Spring异步线程池—传递线程上下文(TaskDecorator实现)

什么是多租户模式?

Spring Cloud Feign 参数上下文设计