ThreadLocal – 使用spring-boot作为REST API的上下文信息

我有一些spring-boot应用程序(它暴露了rest api)。 提到的REST API由spring-security 。 一切都很好,但是现在我需要为服务请求设置上下文。 设置上下文是关于根据用户上下文选择数据源。 关键是RoutingDataSource需要使用这个上下文。 (由于其他原因,必须在validation请求后直接设置此上下文,我还有其他使用RoutingDataSource的线程,但没有请求调用(没有用户上下文))。

我可以做的这些事情,但我的疑虑是关注上下文的线程安全性和清除它。 我试图在文档中找到答案,但我没有成功。

 public class CustomContextHolder { private static final ThreadLocal contextHolder = new ThreadLocal(); public static void setContext(DatabaseType databaseType) { contextHolder.set(databaseType); } public static CustomerType getContext() { return (CustomerType) contextHolder.get(); } public static void clearContext() { contextHolder.remove(); } } 

并设置上下文:

 @Component class AuthorizedRequestFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated()) { // here we set context } filterChain.doFilter(request, response); } } 

我能做到。 但是,因为spring-boot是multithreading的,并且我使用ThreadLocal来保存上下文,所以我担心这种配置的线程安全性。

当我设置这个上下文? 在filter中, 仅在成功授权请求后。 所以问题是:

  1. 它是线程安全的吗? 这意味着:我可以假设执行filter的同一个线程(因此在它自己的本地上下文中也是这个线程集上下文)也执行整个请求(例如从dao调用方法,发送响应,执行控制器的主体)?

  2. 如果在案例1中,我可以假设一个线程从头到尾处理请求(开始包括安全请求后的filter)然后我应该调用clearContext()

在第二个问题上:在您设置它的同一filter中清除本地线程。

  Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); boolean contextSetViaThreadLocal = false; if (authentication != null && authentication.isAuthenticated()) { contextSetViaThreadLocal = true; // here we set context } // immediately after the conditional context store try { filterChain.doFilter(request, response); } finally { if (contextSetViaThreadLocal) { // clear the context } } 
  1. 如果您的程序中只使用一个线程,答案是肯定的。 没有理由在不同的线程中运行此操作,因为切换线程是开销。 但是在您的程序中,您或某人可以定义异步操作(@ Async,Thread.start(),事件等),在这种情况下,有多个线程,并且您的ThreadLocal将仅处理第一个线程的值。

  2. 是的,但请看第一段。

我建议为此任务使用与用户关联的线程安全缓存(例如ConcurrentHashMap)。 理解和线程安全会更简单。 如果您想使用ThreadLocal,您需要澄清并最小化其在您的应用程序中的生命周期。

您应该在请求完成后清除一次上下文。

 try { filterChain.doFilter(request, response); } finally { // remove context here } 

除非你故意启动子线程,否则它是单线程的。 在这种情况下使用InheritableThreadLocal来存储信息。