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閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容

  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,324評論 11 349
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,721評論 18 399
  • 一、多線程 說明下線程的狀態 java中的線程一共有 5 種狀態。 NEW:這種情況指的是,通過 New 關鍵字創...
    Java旅行者閱讀 4,711評論 0 44
  • 前言 ThreadLocal很多同學都搞不懂是什么東西,可以用來干嘛。但面試時卻又經常問到,所以這次我和大家一起學...
    liangzzz閱讀 12,482評論 14 228
  • Gwen陪你讀《愛麗絲漫游奇境記》8.29 Alice considered a little, and then...
    123逍遙游閱讀 324評論 0 0