app后端拋棄session利用token來啟用自己的會話管理

為什么要放棄session

  1. 現(xiàn)在的互聯(lián)網(wǎng)環(huán)境中悼嫉,集群是后臺比較常見的情況,眾所周知薪介,session其實(shí)是一個jvm內(nèi)的用戶副本澜搅,如果我們要把一個集群中的用戶session做共享處理還是比較麻煩的。
  2. app的客戶端對session的支持會比較麻煩

基于上面的兩點(diǎn)粘咖,我們才會想自己來管理這一個會話蚣抗。

ThreadLocal

在提到會話管理這個之前我們需要先了解一個東西ThreadLocal.
那么ThreadLocal是什么呢?

JDK 1.2的版本中就提供java.lang.ThreadLocal瓮下,ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路翰铡。使用這個工具類可以很簡潔地編寫出優(yōu)美的多線程程序,ThreadLocal并不是一個Thread讽坏,而是Thread的局部變量锭魔。

那么我們這個ThreadLocal一般用來做什么事呢?

首先,ThreadLocal 不是用來解決共享對象的多線程訪問問題的路呜,一般情況下迷捧,通過ThreadLocal.set() 到線程中的對象是該線程自己使用的對象织咧,其他線程是不需要訪問的,也訪問不到的漠秋。各個線程中訪問的是不同的對象烦感。

另外,說ThreadLocal使得各線程能夠保持各自獨(dú)立的一個對象膛堤,并不是通過ThreadLocal.set()來實(shí)現(xiàn)的手趣,而是通過每個線程中的new 對象 的操作來創(chuàng)建的對象,每個線程創(chuàng)建一個肥荔,不是什么對象的拷貝或副本绿渣。通過ThreadLocal.set()將這個新創(chuàng)建的對象的引用保存到各線程的自己的一個map中,每個線程都有這樣一個map燕耿,執(zhí)行ThreadLocal.get()時中符,各線程從自己的map中取出放進(jìn)去的對象,因此取出來的是各自自己線程中的對象誉帅,ThreadLocal實(shí)例是作為map的key來使用的淀散。

如果ThreadLocal.set()進(jìn)去的東西本來就是多個線程共享的同一個對象,那么多個線程的ThreadLocal.get()取得的還是這個共享對象本身蚜锨,還是有并發(fā)訪問問題档插。

看了上面的描述,你應(yīng)該能很清晰的明白了ThreadLocal的定義亚再。對郭膛,他就是用Thread作為key來存儲對應(yīng)的線程副本變量的。

如何用ThreadLocal來達(dá)到我們的效果

大家應(yīng)該知道氛悬,我們部署在tomcat容器下的jersey服務(wù)则剃,每次請求都會對應(yīng)著一個新開啟的用戶線程。

這樣也就意味著我們的每次請求都是一個會話開啟到結(jié)束的過程如捅,那么從我們會話開啟的過程中棍现,如何在我們的前置請求中去攔截我們的用戶請求,達(dá)到一個驗(yàn)證是否是我們的用戶镜遣,然后如果是我們的用戶的話己肮,那么他對應(yīng)的是哪個用戶呢?

帶著這樣的疑問烈涮,我們想到了之前我寫的那篇文章jersey利用filter和Dynamic binding來實(shí)現(xiàn)token攔截過濾請求.

在我們的fifter中的請求攔截的時候朴肺,我們會找到我們的token窖剑,根據(jù)token來判斷是否是我們的用戶坚洽。

那么在我們fifter中我就可以做這樣的一件事。我們利用在fifter時候攔截token的用戶鑒別來吧用戶信息存儲到一個中間介質(zhì)的ThreadLocal變量中西土,在我們的下游api層的時候就可以直接去ThreadLocal中取得是哪一個用戶來進(jìn)行的請求讶舰。

但是這其中有一個問題,那就是我們的fifter和下游的api層是不是同一個線程呢?因?yàn)?code>ThreadLocal變量的介質(zhì)如果不是同一個線程就會取不到值跳昼。但是很幸運(yùn)般甲,我們的fifter和下游的api層是在我們的jersey中是同一個線程。

那ok鹅颊,我們的所有規(guī)劃都已完成敷存,那具體的ThreadLocal實(shí)現(xiàn)是什么樣子的呢?

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;
    }

    /**
     * 設(shè)置會話級別的session元素值
     * @param userInfo
     */
    public static void initThreadContext(UserInfo userInfo) {
        context.set(new InvocationContext(userInfo));
    }
}

具體怎么使用我們這一塊的InvocationContext呢堪伍?
首先在fifter的token攔截鑒別用戶成功之后調(diào)用initThreadContext方法傳遞userInfo的信息锚烦,然后在我們的整個api會話層的下游的只需要調(diào)用InvocationContext.getContext().getUserInfo()方法就能獲取本次請求的userInfo的信息了。

那么請注意的一點(diǎn)ThreadLocal是可能引起內(nèi)存泄露的帝雇。

解決ThreadLocal的內(nèi)存泄露

那么ThreadLocal的內(nèi)存泄露是由什么原因引起的呢涮俄?

threadlocal里面使用了一個存在弱引用的map,當(dāng)釋放掉threadlocal的強(qiáng)引用以后,map里面的value卻沒有被回收.而這塊value永遠(yuǎn)不會被訪問到了. 所以存在著內(nèi)存泄露. 最好的做法是將調(diào)用threadlocal的remove方法.

每個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個threadlocal實(shí)例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每個key都弱引用指向threadlocal. 當(dāng)把threadlocal實(shí)例置為null以后,沒有任何強(qiáng)引用指向threadlocal實(shí)例,所以threadlocal將會被gc回收. 但是,我們的value卻不能回收,因?yàn)榇嬖谝粭l從current thread連接過來的強(qiáng)引用. 只有當(dāng)前thread結(jié)束以后, current thread就不會存在棧中,強(qiáng)引用斷開, Current Thread, Map, value將全部被GC回收.

所以得出一個結(jié)論就是只要這個線程對象被gc回收,就不會出現(xiàn)內(nèi)存泄露尸闸,但在threadLocal設(shè)為null和線程結(jié)束這段時間不會被回收的彻亲,就發(fā)生了我們認(rèn)為的內(nèi)存泄露。其實(shí)這是一個對概念理解的不一致吮廉,也沒什么好爭論的苞尝。最要命的是線程對象不被回收的情況,這就發(fā)生了真正意義上的內(nèi)存泄露宦芦。比如使用線程池的時候野来,線程結(jié)束是不會銷毀的,會再次使用的踪旷。就可能出現(xiàn)內(nèi)存泄露曼氛。

PS.Java為了最小化減少內(nèi)存泄露的可能性和影響,在ThreadLocal的get,set的時候都會清除線程Map里所有key為null的value令野。所以最怕的情況就是舀患,threadLocal對象設(shè)null了,開始發(fā)生“內(nèi)存泄露”气破,然后使用線程池聊浅,這個線程結(jié)束,線程放回線程池中不銷毀现使,這個線程一直不被使用低匙,或者分配使用了又不再調(diào)用get,set方法,那么這個期間就會發(fā)生真正的內(nèi)存泄露碳锈。

看了上面的解釋之后顽冶,我們知道在本次線程會話結(jié)束后需要設(shè)置threadlocalset方法為null。并且調(diào)用remove方法就可以解決threadlocal的內(nèi)存泄露問題售碳。

大家應(yīng)該也注意到我上面InvocationContext代碼中的clear方法了强重,那么什么時候該調(diào)用clear方法呢绞呈。
我們是選擇在一次serverlet請求結(jié)束的時候調(diào)用該方法。具體代碼如下:

public class HttpServletRequestListener implements ServletRequestListener {

    /**
     * 銷毀會話session,防止內(nèi)存泄露
     * @param sre
     */
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        InvocationContext.clear();
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {

    }

}

然后在我們的項(xiàng)目中的Clearweb.xml文件中聲明這個listener`就ok了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末间景,一起剝皮案震驚了整個濱河市佃声,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倘要,老刑警劉巖圾亏,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異封拧,居然都是意外死亡召嘶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門哮缺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弄跌,“玉大人,你說我怎么就攤上這事尝苇☆踔唬” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵糠溜,是天一觀的道長淳玩。 經(jīng)常有香客問我,道長非竿,這世上最難降的妖魔是什么蜕着? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮红柱,結(jié)果婚禮上承匣,老公的妹妹穿的比我還像新娘。我一直安慰自己锤悄,他們只是感情好韧骗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著零聚,像睡著了一般袍暴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隶症,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天政模,我揣著相機(jī)與錄音,去河邊找鬼蚂会。 笑死淋样,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颂龙。 我是一名探鬼主播习蓬,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼纽什,長吁一口氣:“原來是場噩夢啊……” “哼措嵌!你這毒婦竟也來了躲叼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤企巢,失蹤者是張志新(化名)和其女友劉穎枫慷,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浪规,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡或听,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了笋婿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片誉裆。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖缸濒,靈堂內(nèi)的尸體忽然破棺而出足丢,到底是詐尸還是另有隱情,我是刑警寧澤庇配,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布斩跌,位于F島的核電站,受9級特大地震影響捞慌,放射性物質(zhì)發(fā)生泄漏耀鸦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一啸澡、第九天 我趴在偏房一處隱蔽的房頂上張望袖订。 院中可真熱鬧,春花似錦嗅虏、人聲如沸著角。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吏口。三九已至,卻和暖如春冰更,著一層夾襖步出監(jiān)牢的瞬間产徊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工蜀细, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舟铜,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓奠衔,卻偏偏與公主長得像谆刨,于是被迫代替她去往敵國和親塘娶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在痊夭,面了一些公司刁岸,掛了不少,但最終還是拿到小米她我、百度虹曙、阿里、京東番舆、新浪酝碳、CVTE、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,246評論 11 349
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法恨狈,類相關(guān)的語法疏哗,內(nèi)部類的語法,繼承相關(guān)的語法禾怠,異常的語法返奉,線程的語...
    子非魚_t_閱讀 31,631評論 18 399
  • 一、多線程 說明下線程的狀態(tài) java中的線程一共有 5 種狀態(tài)刃宵。 NEW:這種情況指的是衡瓶,通過 New 關(guān)鍵字創(chuàng)...
    Java旅行者閱讀 4,679評論 0 44
  • 前言 ThreadLocal很多同學(xué)都搞不懂是什么東西,可以用來干嘛牲证。但面試時卻又經(jīng)常問到哮针,所以這次我和大家一起學(xué)...
    liangzzz閱讀 12,451評論 14 228
  • Gwen陪你讀《愛麗絲漫游奇境記》8.29 Alice considered a little, and then...
    123逍遙游閱讀 312評論 0 0