简体   繁体   English

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

[英]ThreadLocal - using as context information for REST API with spring-boot

I have some spring-boot application (it exposes rest api).我有一些spring-boot应用程序(它公开了 rest api)。 The mentioned REST API is secured by spring-security .提到的 REST API 由spring-security Everything is fine, however now I need to set context for servicing request.一切都很好,但是现在我需要为服务请求设置上下文。 Setting context is about choosing datasource in depends on user context.设置上下文是关于根据用户上下文选择数据源。 The key is that RoutingDataSource need to use this context.关键是 RoutingDataSource 需要用到这个上下文。 (This context must be set directly after authenticating request due to other causes, I have also other thread which use RoutingDataSource, but no invoked by request (no user context)). (由于其他原因,必须在对请求进行身份验证后直接设置此上下文,我还有其他线程使用 RoutingDataSource,但没有被请求调用(无用户上下文))。

These things I can do, however my doubts are concerned on thread-safety of context and clearing it.我可以做这些事情,但是我的疑虑是关于上下文的线程安全性和清除它。 I tried to find answer in docs, but I didn't managed to.我试图在文档中找到答案,但我没有找到。

public class CustomContextHolder {

   private static final ThreadLocal<DatabaseType> contextHolder = 
            new ThreadLocal<DatabaseType>();

   public static void setContext(DatabaseType databaseType) {
      contextHolder.set(databaseType);
   }

   public static CustomerType getContext() {
      return (CustomerType) contextHolder.get();
   }

   public static void clearContext() {
      contextHolder.remove();
   }
}

And setting context:并设置上下文:

@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);
    }
}

I can do it.我能做到。 However, because spring-boot is multi-thread and I am using ThreadLocal for hold context I am afraid of thread-safety of this configuration.但是,因为spring-boot是多线程的,并且我使用ThreadLocal作为保持上下文,所以我担心这种配置的线程安全性。

When I set this context ?当我设置这个上下文时? In filter, only after successful authorization of request.在过滤器中,只有在请求授权成功后 So the questions are:所以问题是:

  1. Is it thread-safe?它是线程安全的吗? It means: Can I assume that the same thread that executes filter (hence also this thread set context in its own local context) also executes entire request (eg calling methods from dao, sending response, executing body of controller) ?这意味着:我可以假设执行过滤器的同一个线程(因此也是这个线程在其自己的本地上下文中设置上下文)也执行整个请求(例如从 dao 调用方法、发送响应、执行控制器主体)?

  2. If in case 1. I can assume that one thread works with request from begin to end (begin includes filter after secured request) then when I should call clearContext() ?如果在情况 1. 我可以假设一个线程从头到尾处理请求(开始包括安全请求后的过滤器),那么我应该什么时候调用clearContext()

it's single threaded unless you purposely initiate child threads.它是单线程的,除非你故意启动子线程。 in such case use InheritableThreadLocal to store information.在这种情况下,使用InheritableThreadLocal来存储信息。

On your second question: clear thread local in the same filter in which you set it.关于您的第二个问题:在您设置它的同一过滤器中清除本地线程。

    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. If you use only one thread in your program, the answer is yes.如果您在程序中只使用一个线程,那么答案是肯定的。 There are no reasons run this operations in different threads, because switching threads is overhead.没有理由在不同的线程中运行此操作,因为切换线程是开销。 But in your program you or somebody can define async operations (@Async, Thread.start(), events, etc.) in that case there are more then one thread, and your ThreadLocal will handle value only for the first thread.但是在您的程序中,您或其他人可以定义异步操作(@Async、Thread.start()、事件等),在这种情况下有多个线程,并且您的 ThreadLocal 将仅处理第一个线程的值。

  2. Yes, but see first paragraph.是的,但请参阅第一段。

I recommend for this task use thread safe cache (for example ConcurrentHashMap) associate with users.我建议为此任务使用与用户关联的线程安全缓存(例如 ConcurrentHashMap)。 It will be simpler for understanding and thread safe.理解和线程安全会更简单。 If you want use ThreadLocal you need to clarify and minimize his lifecycle in your application.如果您想使用 ThreadLocal,您需要在您的应用程序中阐明并最小化他的生命周期。

you should clear context once after request is completed.您应该在请求完成后清除上下文。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM