SpringBoot 異步線程間傳遞上下文方式

異步線程間傳遞上下文

需求

SpringBoot項目中,經常使用@Async來開啟一個子線程來完成異步操作。主線程中的用戶信息需要傳遞給子線程

實現

啟用異步功能

在啟動類裡加上@EnableAsync註解

@EnableAsync
@SpringBootApplication
public class Application {}

配置異步

新建一個配置類,實現AsyncConfigurer接口,並重寫getAsyncExecutor方法

@Configuration
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setThreadNamePrefix("async-pool-");
        // 這一步是關鍵,異步Task裝飾器
        executor.setTaskDecorator(new MyContextDecorator());
        executor.initialize();
        return executor;
    }
}

配置任務裝飾器

新建一個異步任務裝飾器,實現TaskDecorator接口,並重寫decorate方法

public class MyContextDecorator implements TaskDecorator {
    @Override
    @Nonnull
    public Runnable decorate(@Nonnull Runnable runnable) {
  // 獲取主線程中的請求信息(我們的用戶信息也放在裡面)
       RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        return () -> {
            try {
               // 將主線程的請求信息,設置到子線程中
               RequestContextHolder.setRequestAttributes(attributes);
              // 執行子線程,這一步不要忘瞭
                runnable.run();
            } finally {
             // 線程結束,清空這些信息,否則可能造成內存泄漏
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }

補充下:RequestContextHolder內部是基於ThreadLocal實現的,因此在使用set get時,都是和當前線程綁定的。當然,使用者的用戶信息不一定放在瞭RequestContextHolder裡面,讀者可以自行擴展。

到此,通過@Async開啟的子線程,就可以正常拿到父線程中的Request信息瞭。

啟用多線程安全上下文無法在線程間共享問題

問題

項目中多線程添加數據,mybatisplus元數據填充功能,填充創建人時,數據是來自 spring security SecurityContextHolder.getContext.getAuthentication,同步操作時,能正常獲取,而異步執行時空指針異常。

解決方案

配置安全上下文全局策略SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL)

原理

Spring Security 安全上下文默認策略為MODE_THREADLOCAL,ThreadLocal機制來保存每個使用者的安全上下文。

這意味著,隻要針對某個使用者的邏輯執行都是在同一個線程中進行,即使不在各個方法之間以參數的形式傳遞其安全上下文,各個方法也能通過SecurityContextHolder工具獲取到該安全上下文。

隻要在處理完當前使用者的請求之後註意清除ThreadLocal中的安全上下文,這種使用ThreadLocal的方式是很安全的。

  • MODE_GLOBAL: JVM中所有的線程使用同一個安全上下文
  • MODE_INHERITABLETHREADLOCAL:有些應用會有自己的線程創建,並且希望這些新建線程也能使用創建者的安全上下文。這種效果,可以通過將SecurityContextHolder配置成MODE_INHERITABLETHREADLOCAL策略達到。

結果

在配置文件中添加:

@PostConstruct
public void init(){
    SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
 

@PostConstruct註解好多人以為是Spring提供的。其實是Java自己的註解。

Java中該註解的說明:@PostConstruct該註解被用來修飾一個非靜態的void()方法。被@PostConstruct修飾的方法會在服務器加載Servlet的時候運行,並且隻會被服務器執行一次。PostConstruct在構造函數之後執行,init()方法之前執行。

通常我們會是在Spring框架中使用到@PostConstruct註解 該註解的方法在整個Bean初始化中的執行順序:

Constructor(構造方法) -> @Autowired(依賴註入) -> @PostConstruct(註釋的方法)

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: