前言

在我们日常 Java Web 开发中难免遇到需要把一个参数层层的传递到最内层,然后中间层根本不需要使用这个参数,或者是仅仅在特定的工具类中使用,这样我们完全没有必要在每一个方法里面都传递这样一个通用的参数。如果有一个办法能够在任何一个类里面想用的时候直接拿来使用就太好了。JavaWeb项目大部分都是基于Tomcat,每次访问都是一个新的线程,这样让我们联想到了ThreadLocal,每一个线程都独享一个ThreadLocal,在接收请求的时候set特定内容,在需要的时候get这个值。下面我们就进入主题。

ThreadLocal

维持线程封闭性的一种更规范的方法就是使用ThreadLocal,这个类能使线程中的某个值与保存的值的对象关联起来。ThreadLocal提供getset等接口或方法,这些方法为每一个使用这个变量的线程都存有一份独立的副本,因此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方法直接返回当前ThreadthreadLocals变量,这样说明了之所以说ThreadLocal线程局部变量就是因为它只是通过ThreadLocal变量存在了Thread本身而已。

createMap方法

void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

set的时候如果不存在threadLocals,直接创建对象。由上看出,放入mapkey是当前的ThreadLocalvalue是需要存放的内容,所以我们设置属性的时候需要注意存放和获取的是一个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;
}
}

然后准备一个staticThreadLocal类用来存放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类似全局变量,他能降低代码的可重用性,并在类之间引入隐含的耦合性,所以再使用前需要格外小心。

  

  

  

  

  

  

  

  

  

 

优雅的使用 ThreadLocal的更多相关文章

  1. 理解和使用ThreadLocal类

    一.从数据结构入手 下图为ThreadLocal的内部结构图 从上面的机构图,可以窥见ThreadLocal的核心机制: 每个Thread线程内部都有一个Map: Map里面存储线程本地对象(key) ...

  2. javaweb学习总结—Apache的DBUtils框架学习

    注明: 本文转载自http://www.cnblogs.com/xdp-gacl/p/4007225.html 一.commons-dbutils简介 commons-dbutils 是 Apache ...

  3. JavaWeb学习总结(十四)--Apache的DBUtils

    一.commons-dbutils简介 commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化 ...

  4. javaweb学习总结(四十一)——Apache的DBUtils框架学习

    一.commons-dbutils简介 commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化 ...

  5. JavaWeb学习笔记(十六)—— 事务

    一.事务概述 1.1 什么是事务 银行转账!张三转10000块到李四的账户,这其实需要两条SQL语句: 给张三的账户减去10000元: 给李四的账户加上10000元. 如果在第一条SQL语句执行成功后 ...

  6. Apache的DBUtils框架学习(转)

    原文地址:http://www.cnblogs.com/xdp-gacl/p/4007225.html 一.commons-dbutils简介 commons-dbutils 是 Apache 组织提 ...

  7. Apache的DBUtils框架学习(转)

    一.commons-dbutils简介 commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化 ...

  8. 优雅的设计单线程范围内的数据共享(ThreadLocal)

    单线程范围内数据共享使用ThreadLocal /** * @Description TODO * @author zhanghw@chinatelecom.cn * @since 2015年12月1 ...

  9. ThreadLocal中优雅的数据结构如何体现农夫山泉的广告语

    本篇文章主要讲解 ThreadLocal 的用法和内部的数据结构及实现.有时候我们写代码的时候,不太注重类之间的职责划分,经常造出一些上帝类,也就是什么功能都往这个类里放.虽然能实现功能但是并不优雅且 ...

随机推荐

  1. python 字符串(str)

  2. day6_python之pickle、shelve序列化和反序列化

    pickle.shelve,python私有,支持所有python数据类型 一.pickle dic={'name':'egon','age':18} print(pickle.dumps(dic)) ...

  3. 解决 VS 跳转定义和 Resharper 重复

    在大约一周之前,Visual Studio 进行了一项更新,增加了 Ctrl+Click 点击跳转到定义的功能.这项功能与 ReSharper 重复了. 于是可以通过关闭其中一个跳转定义可以使用. V ...

  4. Android 设置ImageView宽度固定,其高度按比例缩放适应

    今天和项目经理对喷了一下,他说在应用的列表数据中的图片应该宽度固定,高度按比例缩放自适应,我说,那岂不是很丑!直接让运营那边把图片处理成固定宽高比不就好了,省的我客户端麻烦了. 这家伙不同意,为毛呢, ...

  5. 将 vue.js 获取的 html 文本转化为纯文本

    我存入数据表中的数据是使用 html  格式,获取数据是使用 vue 获取. 遇到了一个问题,就是界面上显示的数据是 html 格式的,但是我需要它显示纯文本. 怎么做呢?首先在  js  中写一个将 ...

  6. jq常用动画fade slide

    https://www.cnblogs.com/sandraryan/ hide(); <div class="box">big box</div> $(' ...

  7. 洛谷P1449 后缀表达式 题解 栈

    题目链接:https://www.luogu.org/problem/P1449 这道题目我们只需要开一个栈,每次读取到一个数的话就将这个数 push 进栈. 因为提供给我们的时候已经是一个后续序列了 ...

  8. mybatis 逆向工程(通过数据库表针对单表自动生成mybatis执行所需要的代码)

    mybatis需要程序员自己编写sql语句,mybatis官方提供逆向工程,可以针对单表自动生成mybatis执行所需要的代码(mapper.java.mapper.xml.pojo…),可以让程序员 ...

  9. 央行下属的上海资信网络金融征信系统(NFCS)签约机构数量突破800家

    央行下属的上海资信网络金融征信系统(NFCS)签约机构数量突破800家 ——已接入NFCS的P2P网贷机构将优先 接入央行征信系统   P2P网贷行业的健康发展依赖于信用体系的支撑和保障,加快完善适应 ...

  10. 2018-9-1-win2d-画出好看的图形

    title author date CreateTime categories win2d 画出好看的图形 lindexi 2018-09-01 16:25:40 +0800 2018-2-13 17 ...