如何检查当前线程是否通过特定ManagedExecutor执行及关联用户ID
实现方案
要实现这个需求,核心是给每个用户对应的ManagedExecutor绑定用户ID,并让任务执行时能安全获取该ID,同时识别是否通过自定义Executor调用。推荐基于线程上下文传播的方案,符合Jakarta EE规范且适配SmallRye实现,具体步骤如下:
1. 自定义线程上下文提供者管理用户ID
创建一个ThreadContextProvider实现类,专门负责用户ID的上下文捕获、传播和清理:
public class UserIdThreadContextProvider implements ThreadContextProvider { public static final String USER_ID_CONTEXT_NAME = "userIdContext"; private static final ThreadLocal<String> userIdHolder = new ThreadLocal<>(); // 设置当前线程的用户ID public static void setCurrentUserId(String userId) { userIdHolder.set(userId); } // 获取当前线程绑定的用户ID(任务执行时调用) public static String getCurrentUserId() { return userIdHolder.get(); } // 清除当前线程的用户ID,避免污染 public static void clearCurrentUserId() { userIdHolder.remove(); } @Override public ThreadContextSnapshot currentContext(Map<String, String> props) { // 捕获当前线程的用户ID快照 String currentUserId = userIdHolder.get(); return () -> { // 传播上下文:保存原ID,设置当前快照的ID String originalUserId = userIdHolder.get(); userIdHolder.set(currentUserId); // 返回清理回调:恢复原ID return () -> userIdHolder.set(originalUserId); }; } @Override public ThreadContextSnapshot clearedContext(Map<String, String> props) { return () -> { String originalUserId = userIdHolder.get(); userIdHolder.remove(); return () -> userIdHolder.set(originalUserId); }; } @Override public String getThreadContextName() { return USER_ID_CONTEXT_NAME; } }
2. 注册自定义上下文提供者
在项目的META-INF/services目录下创建文件jakarta.enterprise.concurrent.ThreadContextProvider,内容为自定义类的全限定名,让SmallRye能识别并加载这个上下文提供者:
com.yourpackage.UserIdThreadContextProvider
3. 绑定用户ID到ManagedExecutor
修改你的ManagedExecutor构建逻辑,在创建每个用户对应的Executor时,先设置当前线程的用户ID,确保上下文被传播到任务执行线程:
// 假设当前循环处理每个用户ID String userId = "user_123"; // 临时设置当前线程的用户ID,用于上下文快照捕获 UserIdThreadContextProvider.setCurrentUserId(userId); ManagedExecutor managedExecutor = ((SmallRyeManagedExecutor.Builder) ManagedExecutor.builder()) .propagated(Set.of(ThreadContext.ALL_REMAINING, UserIdThreadContextProvider.USER_ID_CONTEXT_NAME)) .maxAsync(1) .maxQueued(Integer.MAX_VALUE) .withExecutorService(parentManagedExecutor) .build(); // 清除当前线程的用户ID,避免污染后续逻辑 UserIdThreadContextProvider.clearCurrentUserId(); // 将Executor存入用户ID映射 userExecutorMap.put(userId, managedExecutor);
4. 在任务中获取用户ID并验证
在通过该ManagedExecutor执行的任务代码里,直接调用工具方法获取用户ID:
public class UserTask implements Runnable { @Override public void run() { String userId = UserIdThreadContextProvider.getCurrentUserId(); if (userId != null) { // 确认是通过自定义ManagedExecutor执行,且拿到了关联的用户ID System.out.println("当前任务属于用户:" + userId); } else { // 非自定义Executor执行的任务 System.out.println("任务未通过指定Executor执行"); } } }
备选方案:包装Executor绑定用户ID
如果不想扩展线程上下文,也可以直接包装ManagedExecutor,在提交任务时注入用户ID:
public class UserBoundExecutor implements ManagedExecutor { private final ManagedExecutor delegate; private final String userId; public UserBoundExecutor(ManagedExecutor delegate, String userId) { this.delegate = delegate; this.userId = userId; } @Override public void execute(Runnable command) { delegate.execute(() -> { try { UserIdThreadContextProvider.setCurrentUserId(userId); command.run(); } finally { UserIdThreadContextProvider.clearCurrentUserId(); } }); } // 重写submit、invokeAll等所有方法,逻辑类似 }
创建映射时用这个包装类替代原Executor即可,任务中获取用户ID的方式和上面一致。
内容的提问来源于stack exchange,提问作者Dmitry Shohov
相关产品推荐
相关产品推荐

