app后端抛弃session利用token来启用自己的会话管理

为什么要放弃session

  1. 现在的互联网环境中,集群是后台比较常见的情况,众所周知,session其实是一个jvm内的用户副本,如果我们要把一个集群中的用户session做共享处理还是比较麻烦的。
  2. app的客户端对session的支持会比较麻烦

基于上面的两点,我们才会想自己来管理这一个会话。

ThreadLocal

在提到会话管理这个之前我们需要先了解一个东西ThreadLocal.
那么ThreadLocal是什么呢?

JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量。

那么我们这个ThreadLocal一般用来做什么事呢?

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。

另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。

如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。

看了上面的描述,你应该能很清晰的明白了ThreadLocal的定义。对,他就是用Thread作为key来存储对应的线程副本变量的。

如何用ThreadLocal来达到我们的效果

大家应该知道,我们部署在tomcat容器下的jersey服务,每次请求都会对应着一个新开启的用户线程。

这样也就意味着我们的每次请求都是一个会话开启到结束的过程,那么从我们会话开启的过程中,如何在我们的前置请求中去拦截我们的用户请求,达到一个验证是否是我们的用户,然后如果是我们的用户的话,那么他对应的是哪个用户呢?

带着这样的疑问,我们想到了之前我写的那篇文章jersey利用filter和Dynamic binding来实现token拦截过滤请求.

在我们的fifter中的请求拦截的时候,我们会找到我们的token,根据token来判断是否是我们的用户。

那么在我们fifter中我就可以做这样的一件事。我们利用在fifter时候拦截token的用户鉴别来吧用户信息存储到一个中间介质的ThreadLocal变量中,在我们的下游api层的时候就可以直接去ThreadLocal中取得是哪一个用户来进行的请求。

但是这其中有一个问题,那就是我们的fifter和下游的api层是不是同一个线程呢?因为ThreadLocal变量的介质如果不是同一个线程就会取不到值。但是很幸运,我们的fifter和下游的api层是在我们的jersey中是同一个线程。

那ok,我们的所有规划都已完成,那具体的ThreadLocal实现是什么样子的呢?

public class InvocationContext {
    private static final ThreadLocal<InvocationContext> context =
        new ThreadLocal<InvocationContext>();

    private UserInfo userInfo;
    private Map<String, String> params;

    private InvocationContext(Map<String, String> params) {
        this.params = params;
    }

    private InvocationContext(UserInfo userInfo) {
        this.userInfo = userInfo;
    }

    public static InvocationContext getContext() {
        return context.get();
    }

    public static void initContext(Map<String, String> params) {
        context.set(new InvocationContext(params));
    }

    public static void clear() {
        context.set(null);
        context.remove();
    }


    public Map<String, String> getParams() {
        return params;
    }

    public void setParams(Map<String, String> params) {
        this.params = params;
    }

    public String getParam(String param) {
        return params.get(param);
    }

    public String getUserId() {
        return userInfo == null ? "" : userInfo.getId();
    }

    public UserInfo getUserInfo() {
        return userInfo;
    }

    public void setUserInfo(UserInfo userInfo) {
        this.userInfo = userInfo;
    }

    /**
     * 设置会话级别的session元素值
     * @param userInfo
     */
    public static void initThreadContext(UserInfo userInfo) {
        context.set(new InvocationContext(userInfo));
    }
}

具体怎么使用我们这一块的InvocationContext呢?
首先在fifter的token拦截鉴别用户成功之后调用initThreadContext方法传递userInfo的信息,然后在我们的整个api会话层的下游的只需要调用InvocationContext.getContext().getUserInfo()方法就能获取本次请求的userInfo的信息了。

那么请注意的一点ThreadLocal是可能引起内存泄露的

解决ThreadLocal的内存泄露

那么ThreadLocal的内存泄露是由什么原因引起的呢?

threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法.

每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.

所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。

PS.Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。所以最怕的情况就是,threadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,那么这个期间就会发生真正的内存泄露。

看了上面的解释之后,我们知道在本次线程会话结束后需要设置threadlocalset方法为null。并且调用remove方法就可以解决threadlocal的内存泄露问题。

大家应该也注意到我上面InvocationContext代码中的clear方法了,那么什么时候该调用clear方法呢。
我们是选择在一次serverlet请求结束的时候调用该方法。具体代码如下:

public class HttpServletRequestListener implements ServletRequestListener {

    /**
     * 销毁会话session,防止内存泄露
     * @param sre
     */
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        InvocationContext.clear();
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {

    }

}

然后在我们的项目中的Clearweb.xml文件中声明这个listener`就ok了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,218评论 11 349
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,602评论 18 399
  • 一、多线程 说明下线程的状态 java中的线程一共有 5 种状态。 NEW:这种情况指的是,通过 New 关键字创...
    Java旅行者阅读 4,667评论 0 44
  • 前言 ThreadLocal很多同学都搞不懂是什么东西,可以用来干嘛。但面试时却又经常问到,所以这次我和大家一起学...
    liangzzz阅读 12,431评论 14 228
  • Gwen陪你读《爱丽丝漫游奇境记》8.29 Alice considered a little, and then...
    123逍遥游阅读 312评论 0 0