title: 自用日志中ThreadLocal的使用 date: 2017.04.15 23:07 categories:
- 技术博客 tags:
- 实现方式
- 日志
接到一个技术类需求,碰到了几个比较有意思的点。
主需求:实现自用的日志记录
基本思路:新增一张systemlog表用于存放数据,利用AOP的思想截获用户的操作行为,增强类中处理具体的操作数据,存入log表,实现分下面两步。
1. 配置增强类AOP
- bean的配置
- AOP的配置(切点,增强 | 通知,切面)
2. 增强类实现
- 该类的作用定位为工具,因此命名为LogUtil,放入util包中(遵循规范)。
- 业务代码
重点是ThreadLocal相关
这次的需求让我回想到了之前关于session的几个点,原因是思路有些重合,看回这个需求,日志的记录要求用户的每次交互都作为数据存入log表,其中表字段设计时需要存入用户对象信息,一见这样的模式就要想到session,可是session无法直接获取,因而需要一个中间层来处理他,这一次就用到了ThreadLocal。
设想一下:用户进入web,每次操作都通过一个触发点将单次请求存起来,每一次的获取我使用拦截器来实现(以后再聊),而放请求这个动作就需要了解Thread的两个小伙伴。
ThreadLocal是与线程绑定的,可以完成这样的需求(log表中记录登录用户)。 核心类:
- Thread:表示线程对象
- ThreadLocal:线程通过该类拷贝进行存值的操作
- ThreadLocalMap:线程自带的容器类,类似Map但有区别,当下没必要纠结其细节
看看JDK1.8源码:
- 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) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }复制代码
原英文注释写的很清楚了:该方法返回当前线程中local变量的副本。
- ThreadLocal中的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); }复制代码
作用:将传入的value放入线程中的map。
- ThreadLocalMap中的set
private void set(ThreadLocal key, Object value) {...}复制代码
作用:将调用该方法的local变量作为key,传入的值作为value放入自定义的map结构中。
关于ThreadLocal的分析网上有很多,今天我不聊过多,说一个思路吧:看源码的时候要学会简化,比如尽可能去抓源码中关键的地方,必要时候配合画类图+脑图的方式,我把类结构简单拿了一下:
因此到这里,我可以试着实现前面的思路,在SharedContext
共用类中添加ThreadLocal成员与静态方法,用于完成对request
的存取:
public static ThreadLocallocal=new ThreadLocal();//本地线程变量public static void setReq(HttpServletRequest req){ local.set(req);//将请求存到那个map中}public static HttpServletRequest getReq(){ return local.get();//拿出来}复制代码
到了LogUtil中业务代码(记录日志功能),我们就可以通过SharedContext共用类获取到当前请求对象(本次是获取session然后拿到user数据),而在取之前要根据自家业务需求在对应的地方存入请求,SharedContext是共用静态的,所以伸缩性很强。
到这里,这个需求的重点之一已经完成了。
小结
- 思路要清晰
- 设计好了就去尝试
- 出问题有很多解决方案(前人的经验)
- 看源码也有技巧(光能读英语还不够)