抱歉問了這么長的問題。。
我對 Spring 相當陌生,還不完全了解內部作業。
因此,我當前的 java 專案有早在 2015 年撰寫的 Spring 4.x 代碼,它使用 ThreadLocal 變數來存盤一些用戶權限資料。
該流程以 REST 控制器中的 REST 呼叫開始,然后呼叫后端代碼并檢查來自資料庫的用戶權限。
有一個@Repository 類,該類具有存盤此用戶權限的 ThreadLocal 的靜態實體。ThreadLocal 變數由呼叫執行緒更新。因此,如果執行緒在已經存在的 ThreadLocal 實體中找到資料,它只會從 ThreadLocal 變數中讀取該資料并開始作業。如果沒有,它會轉到資料庫表并獲取新的權限資料并更新 ThreadLocal 變數。
所以我的理解是使用了 ThreadLocal 變數,因為在同一個 REST 呼叫中多次需要這些用戶權限。所以這個想法是針對給定的 REST 請求,因為執行緒是相同的,它不需要從資料庫中獲取用戶權限,而是可以在同一個 REST 請求中參考它在 ThreadLocal 變數中的條目。
現在,這似乎在 Spring 4.3.29.RELEASE 中作業正常,因為每個 REST 呼叫都由不同的執行緒提供服務。(我列印了執行緒 ID 進行確認。)
Spring 4.x ThreadStack 到 Controller 方法呼叫:
com.xxx.myRESTController.getDoc(MyRESTController.java),
org.springframework.web.context.request.async.WebAsyncManager$5.run(WebAsyncManager.java:332),
java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511),
java.util.concurrent.FutureTask.run(FutureTask.java:266),
java.lang.Thread.run(Thread.java:748)]
但是,當我升級到 Spring 5.2.15.RELEASE 時,這會在呼叫嘗試從后端獲取用戶權限的不同 REST 端點時中斷。
在后端列印 Stacktrace 時,我看到 Spring 5.x 中使用了一個 ThreadPoolExecutor。
Spring 5.x 執行緒堆疊:
com.xxx.myRESTController.getDoc(MyRESTController.java),
org.springframework.web.context.request.async.WebAsyncManager.lambda$startCallableProcessing$4(WebAsyncManager.java:337),
java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511),
java.util.concurrent.FutureTask.run(FutureTask.java:266),
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149),
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624),
java.lang.Thread.run(Thread.java:748)]
所以在 Spring 5.x 中,看起來同一個執行緒被放回到 ThreadPool 中,然后被多個不同的 REST 呼叫呼叫。當此執行緒查找 ThreadLocal 實體時,它會找到它為較早的不相關 REST 呼叫存盤的陳舊資料。因此,由于讀取過時的資料權限,我的很多測驗用例都失敗了。
我讀到呼叫 ThreadLocal 的 remove() 會從變數中清除呼叫執行緒的條目(當時沒有實作)。
我想以通用方式執行此操作,以便所有 REST 呼叫在 REST 回應發回之前呼叫 remove()。
現在,為了清除 ThreadLocal 條目,我嘗試了
通過實作 HandlerInterceptor 撰寫攔截器,但這不起作用。
我還撰寫了另一個擴展 HandlerInterceptorAdapter 并在其 afterCompletion() 中呼叫 ThreadLocal 的 remove() 的攔截器。
然后我嘗試實作 ServletRequestListener 并從它的 requestDestroyed() 方法呼叫 ThreadLocal 的 remove()。
另外,我實作了一個過濾器,并在 doFilter() 方法中呼叫了 remove()。
當我在它們的方法中列印執行緒 ID 時,所有這 4 個實作都失敗了,它們彼此完全相同,但與 RestController 方法中列印的執行緒 ID 不同。
因此,呼叫 REST 端點的執行緒與上述 4 個類呼叫的執行緒是不同的執行緒。因此,上述類中的 remove() 呼叫永遠不會從 ThreadLocal 變數中清除任何內容。
有人可以提供一些有關如何在 Spring 中以通用方式清除給定執行緒的 ThreadLocal 條目的指示嗎?
uj5u.com熱心網友回復:
如您所見, theHandlerInterceptor和 theServletRequestListener都在原始 servlet 容器執行緒中執行,在那里接收請求。由于您正在進行異步處理,因此您需要一個CallableProcessingInterceptor.
它的preProcess和postProcess方法在將發生異步處理的執行緒上執行。
因此你需要這樣的東西:
WebAsyncUtils.getAsyncManager(request)//
.registerCallableInterceptor("some_unique_key", new CallableProcessingInterceptor() {
@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task,
Object concurrentResult) throws Exception {
// remove the ThreadLocal
}
});
在可以訪問ServletRequest并在原始servlet 容器執行緒中執行的HandlerInterceptor#preHandle方法中,例如在方法中。
備注:ThreadLocal您可以使用 Spring 的RequestAttributes. 使用靜態方法:
RequestContextHolder.currentRequestAttributes()
檢索當前實體。在底層使用了 a ThreadLocal,但 Spring 負責在處理請求的每個執行緒(包括異步處理)上設定和洗掉它。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/366730.html
