一、前言
最近在做一個專案,有個比較耗時的操作是啟用執行緒進行異步操作,當時在啟用的執行緒時,突然發現子執行緒無法獲取父執行緒中的HttpServletRequest請求物件,因為是第一次遇到這種問題,所以記錄一下解決方案,
二、問題模擬
在這里,我們簡單模擬一下出現的問題,我們首先撰寫一個簡單的hello請求,代碼如下:
/**
* 主執行緒獲取
* @return
*/
@GetMapping("/hello")
public String hello() {
String name = "";
HttpServletRequest request = RequestUtils.getRequest();
if (null == request) {
log.info("未獲取到request物件!");
} else {
name = request.getParameter("name");
log.info("獲取到的內容為{}", name);
}
return "hello";
}
這是一個正常的請求,我們啟動專案,訪問介面地址,


從上圖中,我們不難發現,我們成功的拿到了HttpServletRequest中的引數,
接著,我們稍微修改一下我們的代碼,另起一個執行緒,在子執行緒中獲取HttpServletRequest中的name屬性,代碼如下:
/**
* 主執行緒獲取
* @return
*/
@GetMapping("/hello")
public String hello() {
new Thread(() -> {
HttpServletRequest request = RequestUtils.getRequest();
if (null == request) {
log.info("未獲取到request物件!");
} else {
String name = request.getParameter("name");
log.info("獲取到的內容為{}", name);
}
}).start();
return "hello";
}
我們再次啟動專案并訪問介面地址:


我們發現,這時候的request物件已經變為空,我們根本沒辦法獲取請求中的name屬性,
結論:如果采用多執行緒,我們就獲取不到父執行緒中的HttpServletRequest物件了,
三、解決方法
解決上面的問題其實很簡單,只需要在開啟子執行緒時,呼叫一下 RequestContextHolder.setRequestAttributes(requestAttributes, true);方法,將第二個引數設為true就可以了,
我們修改上面的代碼如下:
/**
* 主執行緒獲取
* @return
*/
@GetMapping("/hello")
public String hello() {
/**
* 解決子執行緒無法獲取HttpServletRequest請求物件中資料的問題
*/
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
RequestContextHolder.setRequestAttributes(requestAttributes, true);
new Thread(() -> {
HttpServletRequest request = RequestUtils.getRequest();
if (null == request) {
log.info("未獲取到request物件!");
} else {
String name = request.getParameter("name");
log.info("獲取到的內容為{}", name);
}
}).start();
return "hello";
}
啟動專案,訪問介面地址,結果如下:


可以發現,我們可以在子執行緒中獲取HttpServletRequest物件了,
四、原理
點開RequestContextHolder.setRequestAttributes(requestAttributes, true)方法,查看原始碼:
/**
* Bind the given RequestAttributes to the current thread.
* @param attributes the RequestAttributes to expose,
* or {@code null} to reset the thread-bound context
* @param inheritable whether to expose the RequestAttributes as inheritable
* for child threads (using an {@link InheritableThreadLocal})
*/
public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
if (attributes == null) {
resetRequestAttributes();
}
else {
if (inheritable) {
inheritableRequestAttributesHolder.set(attributes);
requestAttributesHolder.remove();
}
else {
requestAttributesHolder.set(attributes);
inheritableRequestAttributesHolder.remove();
}
}
}
這個方法很簡單,主要只要繼續查看requestAttributesHolder與inheritableRequestAttributesHolder這兩個類的繼承關系就可以了,
requestAttributesHolder
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
public class NamedThreadLocal<T> extends ThreadLocal<T> {}
我們發現,requestAttributesHolder物件型別為NamedThreadLocal,NamedThreadLocal父類是ThreadLocal,
inheritableRequestAttributesHolder
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
public class NamedInheritableThreadLocal<T> extends InheritableThreadLocal<T> {}
我們發現inheritableRequestAttributesHolder的型別為NamedInheritableThreadLocal,NamedInheritableThreadLocal是InheritableThreadLocal的子類,
看到這里,就很清晰了,呼叫RequestContextHolder.setRequestAttributes(requestAttributes, true)這個方法,將原本放在ThreadLocal物件中的屬性放到了型別為InheritableThreadLocal的物件中了,所以我們啟動的子執行緒可以獲取到父執行緒中的屬性,
五、總結
當子執行緒中無法獲取父執行緒中的HttpServletRequest的方法時,我們可以通過呼叫RequestContextHolder.setRequestAttributes(requestAttributes, true)方法,使得子執行緒也可以獲取父執行緒中HttpServletRequest物件,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/557005.html
標籤:其他
下一篇:返回列表
