我們知道 Java Web Tomcat ThreadLocal ThreadLocal set get
的
項目大部分都是基于
,每次通路都是一個新的線程,看到這裡讓我們聯想到了
,每一個線程都獨享一個
,在接收請求的時候
特定内容,在需要的時候
這個值。下面我們就進入主題。
ThreadLocal
維持線程封閉性的一種更規範的方法就是使用
ThreadLocal
,這個類能使線程中的某個值與儲存的值的對象關聯起來。 ThreadLocal
提供 get
和 set
等接口或方法,這些方法為每一個使用這個變量的線程都存有一份獨立的副本,是以 get
總是傳回由目前線程在調用 set
時設定的最新值。 ThreadLocal
有如下方法 -
public T get() { }
-
public void set(T value) { }
-
public void remove() { }
-
protected T initialValue() { }
get()
方法是用來擷取
ThreadLocal
在目前線程中儲存的變量副本
set()
用來設定目前線程中變量的副本
remove()
用來移除目前線程中變量的副本
initialValue()
是一個
protected
方法,一般是用來在使用時進行重寫的,如果在沒有set的時候就調用
get
,會調用
initialValue
方法初始化内容。
為了使用的更放心,我們簡單的看一下具體的實作:
set
方法
-
public void set(T value) {
-
Thread t = Thread.currentThread();
-
ThreadLocalMap map = getMap(t);
-
if (map != null)
-
map.set(this, value);
-
else
-
createMap(t, value);
-
}
set
方法會擷取目前的線程,通過目前線程擷取
ThreadLocalMap
對象。然後把需要存儲的值放到這個
map
裡面。如果沒有就調用
createMap
建立對象。
getMap
-
ThreadLocalMap getMap(Thread t) {
-
return t.threadLocals;
-
}
getMap
方法直接傳回目前
Thread
threadLocals
變量,這樣說明了之是以說
ThreadLocal
是
線程局部變量
就是因為它隻是通過
ThreadLocal
把
變量
存在了
Thread
本身而已。
createMap
-
void createMap(Thread t, T firstValue) {
-
t.threadLocals = new ThreadLocalMap(this, firstValue);
-
}
在
set
的時候如果不存在
threadLocals
,直接建立對象。由上看出,放入
map
key
是目前的
ThreadLocal
,
value
是需要存放的内容,是以我們設定屬性的時候需要注意存放和擷取的是一個
ThreadLocal
。
get方法
-
public T get() {
-
Thread t = Thread.currentThread();
-
ThreadLocalMap map = getMap(t);
-
if (map != null) {
-
ThreadLocalMap.Entry e = map.getEntry(this);
-
if (e != null)
-
return (T)e.value;
-
}
-
return setInitialValue();
-
}
get
方法就比較簡單,擷取目前線程,嘗試擷取目前線程裡面的
threadLocals
,如果沒有擷取到就調用
setInitialValue
方法,
setInitialValue
基本和
set
是一樣的,就不累累述了。
場景
本文應用
ThreadLocal
的場景:在調用API接口的時候傳遞了一些公共參數,這些公共參數攜帶了一些裝置資訊,服務端接口根據不同的資訊組裝不同的格式資料傳回給用戶端。假定伺服器端需要通過裝置類型(device)來下發下載下傳位址,當然接口也有同樣的其他邏輯,我們隻要在傳回資料的時候判斷好是什麼類型的用戶端就好了。如下:
場景一
請求
-
GET api/users?device=android
傳回
-
{
-
user : {
-
},
-
link : "https://play.google.com/store/apps/details?id=***"
-
}
場景二
-
GET api/users?device=ios
-
{
-
user : {
-
},
-
link : "https://itunes.apple.com/us/app/**"
-
}
實作
首先準備一個
BaseSigntureRequest
類用來存放公共參數
-
public class BaseSignatureRequest {
-
private String device;
-
public String getDevice() {
-
return device;
-
}
-
public void setDevice(String device) {
-
this.device = device;
-
}
-
}
然後準備一個
static
ThreadLocal
類用來存放
ThreadLocal
,以便存儲和擷取時候的
ThreadLocal
一緻。
-
public class ThreadLocalCache {
-
public static ThreadLocal<BaseSignatureRequest>
-
baseSignatureRequestThreadLocal = new ThreadLocal<>();
-
}
然後編寫一個
Interceptor
,在請求的時候擷取
device
參數,存入目前線程的
ThreadLocal
中。這裡需要注意的是,重寫了
afterCompletion
方法,當請求結束的時候把
ThreadLocal
remove
,移除不必須要鍵值對。
-
public class ParameterInterceptor implements HandlerInterceptor {
-
@Override
-
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
-
Object handler) throws Exception {
-
String device = request.getParameter("device");
-
BaseSignatureRequest baseSignatureRequest = new BaseSignatureRequest();
-
baseSignatureRequest.setDevice(device);
-
ThreadLocalCache.baseSignatureRequestThreadLocal.set(baseSignatureRequest);
-
return true;
-
}
-
@Override
-
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
-
Object handler, Exception ex) throws Exception {
-
ThreadLocalCache.baseSignatureRequestThreadLocal.remove();
-
}
-
@Override
-
public void postHandle(HttpServletRequest httpServletRequest,
-
HttpServletResponse httpServletResponse,
-
Object o, ModelAndView modelAndView) throws Exception {
-
}
-
}
當然需要在
spring
裡面配置
interceptor
-
<mvc:interceptors>
-
<mvc:interceptor>
-
<mvc:mapping path="/api/**"/>
-
<bean class="life.majiang.ParameterInterceptor"></bean>
-
</mvc:interceptor>
-
</mvc:interceptors>
最後在
Converter
裡面轉換實體的時候直接使用即可,這樣就大功告成了。
-
public class UserConverter {
-
public static ResultDO toDO(User user) {
-
ResultDO resultDO = new ResultDO();
-
resultDO.setUser(user);
-
BaseSignatureRequest baseSignatureRequest = ThreadLocalCache.baseSignatureRequestThreadLocal.get();
-
String device = baseSignatureRequest.getDevice();
-
if (StringUtils.equals(device, "ios")) {
-
resultDO.setLink("https://itunes.apple.com/us/app/**");
-
} else {
-
resultDO.setLink("https://play.google.com/store/apps/details?id=***");
-
}
-
return resultDO;
-
}
總結
這種機制很友善,因為他避免了在調用每一個方法時都要傳遞執行上下文資訊,合理的使用
ThreadLocal
可以起到事倍功半的效果,但是需要避免濫用,例如将所有的全局變量作為
ThreadLocal
對象,
ThreadLocal
類似全局變量,他能降低代碼的可重用性,并在類之間引入隐含的耦合性,是以再使用前需要格外小心。
原文釋出時間為:2018-09-15
本文作者:麻醬
本文來自雲栖社群合作夥伴“
Web項目聚集地”,了解相關資訊可以關注“
”。