【问题标题】:Multithreading within Spring Controller "No thread-bound request found"Spring Controller 中的多线程“未找到线程绑定请求”
【发布时间】:2019-01-02 17:23:48
【问题描述】:

我正在尝试在我的 Spring 应用程序中使用并行流,但出现“未找到线程绑定请求”异常。

我的代码如下所示:

@Controller
@RequiredArgsConstructor(onConstructor = @__(@Inject))  // yes, I'm using lombok
public class controllerClass {
   private final someOtherComponent;

   @RequestMapping(value = "/test", method = RequestMethod.Get)
   public Map<String, String> doParallelStream() {
       List<String> testStrings = Arrays.asList("one", "two", "three");

       return testStrings.parallelStream()
           .map(testStrings -> someOtherComponent.someCall(testStrings))
           .collect(Collectors.toConcurrentMap(
                    returnedString, returnedString, (p1, p2) -> p1
            ));
   }
}

我的猜测是,因为我在并行流内的映射中使用 someOtherComponent,线程启动的线程不再具有访问它的上下文。

我得到的完整错误是:

执行控制器时出错 { java.lang.IllegalStateException: java.lang.IllegalStateException: 未找到线程绑定请求:您是指实际 Web 请求之外的请求属性,还是在原始接收线程之外处理请求?如果您实际上是在 Web 请求中操作并且仍然收到此消息,则您的代码可能在 DispatcherServlet/DispatcherPortlet 之外运行:在这种情况下,请使用 RequestContextListener 或 RequestContextFilter 来公开当前请求。

关于如何解决这个问题的任何建议?

【问题讨论】:

  • 您不必依赖 Spring 的线程绑定魔法。例如,您的 someOtherComponent.someCall 可能使用请求属性自动连接 - 您需要手动连接这些。
  • 不相关,但对于必需的参数,自 Spring 4.1 或 4.2 以来不需要 @Inject@Autowired,这可以清理您的 lombok 使用。由于使用了 parallelStreams,您的假设是正确的。

标签: java spring multithreading


【解决方案1】:

我相信你的假设是正确的。似乎具有requestsession 范围的组件在控制器线程以外的线程中使用。并且由于 RequestContextHolderThreadLocal 的基础使用而引发异常,该 RequestContextHolder 用作 requestsession 范围 bean 的存储。要使其工作,应使用InheritableThreadLocal。您可以通过在DispatcherServletRequestContextFilter 中将threadContextInheritable 属性设置为true 来启用它。

【讨论】:

    【解决方案2】:

    我遇到了同样的错误,但在不同的情况下。我有:

    public ClientSettingsDTO getClientSettings(String clientId) {
    
        CompletableFuture<Boolean> blacklistStatus = CompletableFuture.supplyAsync( () -> {
                return getBlacklistStatus(clientId);
            }); 
        }
    
        private Boolean getBlacklistStatus(String clientId) {
                return mmmBlacklistRestClient.getBlacklistClientById(clientId); // IllegalStateException
        }
    

    通过配置我自己的 Executor bean 并指定任务装饰器解决了这个问题:

       @Bean(name = "executorAsyncThread")
       public TaskExecutor getAccountAsyncExecutor() {
          ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor();
          poolExecutor.setTaskDecorator(new ContextCopyingDecorator());
          poolExecutor.setCorePoolSize(10);
          poolExecutor.setMaxPoolSize(20);
          poolExecutor.setQueueCapacity(80000);
          poolExecutor.setThreadNamePrefix("Async-Executor-");
          poolExecutor.initialize();
          return poolExecutor;
       }
    
    public class ContextCopyingDecorator implements TaskDecorator {
       @Nonnull
       @Override
       public Runnable decorate(@Nonnull Runnable runnable) {
          RequestAttributes context = RequestContextHolder.currentRequestAttributes();
          Map<String, String> contextMap = MDC.getCopyOfContextMap();
          return () -> {
             try {
                RequestContextHolder.setRequestAttributes(context);
                MDC.setContextMap(contextMap);
                runnable.run();
             } finally {
                MDC.clear();
                RequestContextHolder.resetRequestAttributes();
             }
          };
       }
    }
    

    并将其作为supplyAsync 中的第二个参数传递:

            CompletableFuture<Boolean> blacklistStatus = CompletableFuture.supplyAsync( () -> {
                return getBlacklistStatus(clientId);
            }, executor);
    

    这允许执行请求。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-24
      • 1970-01-01
      • 2014-02-12
      • 2016-12-24
      相关资源
      最近更新 更多