优雅的使用 ThreadLocal

优雅的使用 ThreadLocal作者 | 麻酱公号 | 码匠笔记导读:在我们日常 Web 开发中难免遇到需要把一个参数层层的传递到最内层,然后中间层根本不需要使用这个参数,或者

大家好,欢迎来到IT知识分享网。

优雅的使用 ThreadLocal

作者 | 麻酱

公号 | 码匠笔记

导读: 在我们日常 Web 开发中难免遇到需要把一个参数层层的传递到最内层,然后中间层根本不需要使用这个参数,或者是仅仅在特定的工具类中使用,这样我们完全没有必要在每一个方法里面都传递这样一个 通用的参数。如果有一个办法能够在任何一个类里面想用的时候直接拿来使用就太好了。
我们知道
Java

Web
项目大部分都是基于
Tomcat
,每次访问都是一个新的线程,看到这里让我们联想到了
ThreadLocal
,每一个线程都独享一个
ThreadLocal
,在接收请求的时候
set
特定内容,在需要的时候
get
这个值。下面我们就进入主题。
ThreadLocal
维持线程封闭性的一种更规范的方法就是使用
ThreadLocal
,这个类能使线程中的某个值与保存的值的对象关联起来。
ThreadLocal
提供
get

set
等接口或方法,这些方法为每一个使用这个变量的线程都存有一份独立的副本,因此
get
总是返回由当前线程在调用
set
时设置的最新值。
ThreadLocal
有如下方法
 
   
  1. public T get() { }

  2. publicvoid set(T value) { }

  3. publicvoid remove() { }

  4. protected T initialValue() { }

get()方法是用来获取 ThreadLocal在当前线程中保存的变量副本
set()用来设置当前线程中变量的副本
remove()用来移除当前线程中变量的副本
initialValue()是一个 protected方法,一般是用来在使用时进行重写的,如果在没有set的时候就调用 get,会调用 initialValue方法初始化内容。为了使用的更放心,我们简单的看一下具体的实现:

set方法

 
   
  1. publicvoid set(T value) {

  2. Thread t = Thread.currentThread();

  3. ThreadLocalMap map = getMap(t);

  4. if(map != )

  5. map.set(this, value);

  6. else

  7. createMap(t, value);

  8. }

set方法会获取当前的线程,通过当前线程获取
ThreadLocalMap对象。然后把需要存储的值放到这个
map里面。如果没有就调用
createMap创建对象。

getMap方法

 
   
  1. ThreadLocalMap getMap(Thread t) {

  2. return t.threadLocals;

  3. }

ge
tMap
方法直接返回当前
Thread

threadLocals
变量,这样说明了之所以说
ThreadLocal

线程局部变量
就是因为它只是通过
ThreadLocal

变量
存在了
Thread
本身而已

createMap方法

 
   
  1. void createMap(Thread t, T firstValue) {

  2. t.threadLocals = newThreadLocalMap(this, firstValue);

  3. }


set
的时候如果不存在
threadLocals
,直接创建对象。由上看出,放入
map

key
是当前的
ThreadLocal
value
是需要存放的内容,所以我们设置属性的时候需要注意存放和获取的是一个
ThreadLocal

get方法

 
   
  1. public T get() {

  2. Thread t = Thread.currentThread();

  3. ThreadLocalMap map = getMap(t);

  4. if(map != ) {

  5. ThreadLocalMap.Entry e = map.getEntry(this);

  6. if(e != )

  7. return(T)e.value;

  8. }

  9. return setInitialValue();

  10. }

get
方法就比较简单,获取当前线程,尝试获取当前线程里面的
threadLocals
,如果没有获取到就调用
setInitialValue
方法,
setInitialValue
基本和
set
是一样的,就不累累述了。

场景

本文应用
ThreadLocal

场景一

请求

 
   
  1. GET api/users?device=android

返回

 
   
  1. {

  2. user : {

  3. },

  4. link : "https://play.google.com/store/apps/details?id=***"

  5. }

场景二

请求

 
   
  1. GET api/users?device=ios

返回

 
   
  1. {

  2. user : {

  3. },

  4. link : "https://itunes.apple.com/us/app/**"

  5. }

实现

首先准备一个 BaseSigntureRequest类用来存放公共参数

 
   
  1. publicclassBaseSignatureRequest{

  2. privateString device;


  3. publicString getDevice() {

  4. return device;

  5. }


  6. publicvoid setDevice(String device) {

  7. this.device = device;

  8. }

  9. }

然后准备一个 staticThreadLocal类用来存放 ThreadLocal,以便存储和获取时候的 ThreadLocal一致。

 
   
  1. publicclassThreadLocalCache{

  2. publicstaticThreadLocal<BaseSignatureRequest>

  3. baseSignatureRequestThreadLocal = newThreadLocal<>();

  4. }

然后编写一个 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

 
   
  1. <mvc:interceptors>

  2. <mvc:interceptor>

  3. <mvc:mappingpath="/api/**"/>

  4. <beanclass="life.majiang.ParameterInterceptor"></bean>

  5. </mvc:interceptor>

  6. </mvc:interceptors>

最后在 Converter里面转换实体的时候直接使用即可,这样就大功告成了。

 
   
  1. publicclassUserConverter{

  2. publicstaticResultDO toDO(User user) {

  3. ResultDO resultDO = newResultDO();

  4. resultDO.setUser(user);

  5. BaseSignatureRequest baseSignatureRequest = ThreadLocalCache.baseSignatureRequestThreadLocal.get();

  6. String device = baseSignatureRequest.getDevice();

  7. if(StringUtils.equals(device, "ios")) {

  8. resultDO.setLink("https://itunes.apple.com/us/app/**");

  9. } else{

  10. resultDO.setLink("https://play.google.com/store/apps/details?id=***");

  11. }

  12. return resultDO;

  13. }

总结


种机制很方便,因为他避免了在调用每一个方法时都要传递执行上下文信息,合理的使用
ThreadLocal
可以起到事倍功半的效果,但是需要避免滥用,例如将所有的全局变量作为
ThreadLocal
对象,
ThreadLocal
类似全局变量,他能降低代码的可重用性,并在类之间引入隐含的耦合性,所以再使用前需要格外小心。
优雅的使用 ThreadLocal

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/56660.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信