ThreadLocal 方式存儲用戶信息

因為每次業(yè)務(wù)層 或者 其他非 Controller 層,需要用到用戶信息的時候棠耕,就需要把當(dāng)前用戶信息先查詢出來在傳遞毡们,或者是直接把當(dāng)前的 session 直接往下層傳遞。這樣很繁瑣昧辽。
可以使用 線程封閉 性來優(yōu)化這個問題。

最關(guān)鍵的地方在于:

1.每次請求的時候登颓,可能不是同一個線程去執(zhí)行搅荞,會到導(dǎo)致用戶數(shù)據(jù)獲取不到。
2.線程要釋放綁定的用戶數(shù)據(jù)框咙,不然會出現(xiàn)內(nèi)存泄露的問題咕痛。

先創(chuàng)建 ThreadLocal 操作類

........
import lombok.extern.slf4j.Slf4j;

/**
 * session 操作
 * @author weiximei
 */
@Slf4j
public class SessionLoad {

    private static ThreadLocal<SysUser> threadLocal = new ThreadLocal<>();

    /**
     * 設(shè)置用戶信息
     * @param user
     */
    public static void setUser(SysUser user){
        log.info("當(dāng)前線程 --- [{}] --- 設(shè)置用戶 {} ", Thread.currentThread().getName(), user);
        threadLocal.set(user);
    }

    /**
     * 獲取用戶信息
     * @return
     */
    public static SysUser getUser() {
        SysUser user = threadLocal.get();
        log.info("當(dāng)前線程 --- [{}] --- 獲取用戶 {} ", Thread.currentThread().getName(), user);
        return user;
    }

    /**
     * 移除用戶
     */
    public static void removeUser() {
        threadLocal.remove();
    }

}

用戶session操作類,針對 ThreadLocal 操作二次封裝

........
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpSession;

/**
 * 用戶session
 * @author weiximei
 */
@Component
@Slf4j
public class UserControl {

    public static final String SESSION_USER = "user";

    /**
     * 設(shè)置用戶
     * @param user
     */
    public static void setUser(SysUser user) {
        if (null == user) {
            throw new FoyoException("用戶信息不能為空");
        }
        HttpSession session = getSession();
        user.setToken(session.getId());
        session.setAttribute(SESSION_USER, user);
        log.info("當(dāng)前用戶sessionId: {}", session.getId());
        SessionLoad.setUser(user);
    }

    /**
     * 獲取當(dāng)前用戶
     * @return
     */
    public static SysUser getUser() {
        HttpSession session = getSession();
        log.info("當(dāng)前用戶sessionId: {}", session.getId());

        SysUser sysUser = SessionLoad.getUser();
        if (null == sysUser) {
            throw new FoyoException("用戶未登錄");
        }

        return sysUser;
    }

    /**
     * 移除用戶
     */
    public static void removeUser() {
        HttpSession session = getSession();
        session.removeAttribute(SESSION_USER);
        log.info("當(dāng)前用戶sessionId: {}, 退出登錄", session.getId());

        SessionLoad.removeUser();
    }

    /**
     * 獲取當(dāng)前 Request
     */
    private static HttpSession getSession() {
        HttpSession session = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest().getSession();
        return session;
    }
}

定義一個 Filter 解決喇嘱,每次請求不是同一個線程茉贡,造成用戶信息獲取不到問題

.......
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 過濾器
 * @author weiximei
 */
@Component
public class FoyoFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        if (request.getRequestURI().equals("/admin/login")) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }

        SysUser user = SessionLoad.getUser();
        if (null == user) {
            user = (SysUser) request.getSession().getAttribute(UserControl.SESSION_USER);
            if (null == user) {
                // 重定向登錄
                request.getRequestDispatcher("/admin/toLogin").forward(servletRequest, servletResponse);
                return;
            }
        }

        // 設(shè)置用戶
        UserControl.setUser(user);

        // 繼續(xù)執(zhí)行
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

定義一個 Request 上下文監(jiān)聽器,用于釋放線程綁定的用戶信息者铜,避免內(nèi)存泄露
這里也可以選擇 Spring Mvc提供的攔截器腔丧。但是我這里選擇用 Servlet 的原生接口實現(xiàn)

.......
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;

/**
 * web 請求監(jiān)聽器
 * @author weiximei
 */
@Component
@Slf4j
public class RequestListener implements ServletRequestListener {

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        HttpServletRequest request  = (HttpServletRequest) sre.getServletRequest();
        log.info("當(dāng)前線程 --- [{}] --- 銷毀請求:{}", Thread.currentThread().getName() , request.getRequestURI());

       /**
         * 移除當(dāng)前線程綁定的用戶數(shù)據(jù),防止內(nèi)存泄露
         */
        SessionLoad.removeUser();
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        HttpServletRequest request  = (HttpServletRequest) sre.getServletRequest();
        log.info("當(dāng)前線程 --- [{}] --- 創(chuàng)建請求:{}", Thread.currentThread().getName() ,request.getRequestURI());
    }
}

看看結(jié)果:

2019-09-02 15:51:57.392  INFO 15848 --- [nio-8080-exec-3] com.huiju.foyo.filter.RequestListener    : 當(dāng)前線程 ---  [http-nio-8080-exec-3] --- 創(chuàng)建請求:/admin/user/info
2019-09-02 15:51:57.392  INFO 15848 --- [nio-8080-exec-3] com.huiju.foyo.session.SessionLoad       : 當(dāng)前線程 --- [http-nio-8080-exec-3] --- 獲取用戶 null 
2019-09-02 15:51:57.392  INFO 15848 --- [nio-8080-exec-3] com.huiju.foyo.session.UserControl       : 當(dāng)前用戶sessionId: 037D8E592F00E126463DA83A73EA4111
2019-09-02 15:51:57.392  INFO 15848 --- [nio-8080-exec-3] com.huiju.foyo.session.SessionLoad       : 當(dāng)前線程 --- [http-nio-8080-exec-3] --- 設(shè)置用戶 SysUser(userId=1, name=admin, avatar=https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif, loginName=admin, passwd=null, status=1, token=037D8E592F00E126463DA83A73EA4111) 
2019-09-02 15:51:57.393  INFO 15848 --- [nio-8080-exec-3] com.huiju.foyo.session.UserControl       : 當(dāng)前用戶sessionId: 037D8E592F00E126463DA83A73EA4111
2019-09-02 15:51:57.393  INFO 15848 --- [nio-8080-exec-3] com.huiju.foyo.session.SessionLoad       : 當(dāng)前線程 --- [http-nio-8080-exec-3] --- 獲取用戶 SysUser(userId=1, name=admin, avatar=https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif, loginName=admin, passwd=null, status=1, token=037D8E592F00E126463DA83A73EA4111) 
2019-09-02 15:51:57.394  INFO 15848 --- [nio-8080-exec-3] com.huiju.foyo.filter.RequestListener    : 當(dāng)前線程 ---  [http-nio-8080-exec-3] --- 銷毀請求:/admin/user/info

監(jiān)聽器Listener 和 Filter

Servlet監(jiān)聽器用于監(jiān)聽一些重要事件的發(fā)生作烟,監(jiān)聽器對象可以在事情發(fā)生前愉粤、發(fā)生后可以做一些必要的處理。下面將介紹幾種常用的監(jiān)聽器拿撩,以及它們都適合運用于那些環(huán)境衣厘。
分類及介紹:
1.ServletContextListener:用于監(jiān)聽WEB 應(yīng)用啟動和銷毀的事件,監(jiān)聽器類需要實現(xiàn)javax.servlet.ServletContextListener 接口压恒。
2.ServletContextAttributeListener:用于監(jiān)聽WEB應(yīng)用屬性改變的事件影暴,包括:增加屬性、刪除屬性探赫、修改屬性型宙,監(jiān)聽器類需要實現(xiàn)javax.servlet.ServletContextAttributeListener接口。
3.ServletRequestListener : 用于監(jiān)聽請求的創(chuàng)建于銷毀期吓, 需要實現(xiàn) javax.servlet.ServletRequestListener? 接口
4.ServletRequestAttributeListener:請求屬性事件監(jiān)聽器早歇。用于監(jiān)聽請求中的屬性改變的事件倾芝。
5.HttpSessionListener:用于監(jiān)聽Session對象的創(chuàng)建和銷毀,監(jiān)聽器類需要實現(xiàn)javax.servlet.http.HttpSessionListener接口或者javax.servlet.http.HttpSessionActivationListener接口箭跳,或者兩個都實現(xiàn)晨另。
6.HttpSessionActivationListener:用于監(jiān)聽Session對象的鈍化/活化事件,監(jiān)聽器類需要實現(xiàn)javax.servlet.http.HttpSessionListener接口或者javax.servlet.http.HttpSessionActivationListener接口谱姓,或者兩個都實現(xiàn)借尿。
7.HttpSessionAttributeListener:用于監(jiān)聽Session對象屬性的改變事件,監(jiān)聽器類需要實現(xiàn)
8.HttpSessionBindingListener:會話值綁定事件監(jiān)聽器屉来。這是唯一不需要在web.xml中設(shè)定的Listener路翻。
9.Filter:用于在客戶端的請求訪問后端資源之前,攔截這些請求 或者 在服務(wù)器的響應(yīng)發(fā)送回客戶端之前茄靠,處理這些響應(yīng)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茂契,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子慨绳,更是在濱河造成了極大的恐慌掉冶,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脐雪,死亡現(xiàn)場離奇詭異厌小,居然都是意外死亡,警方通過查閱死者的電腦和手機战秋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門璧亚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脂信,你說我怎么就攤上這事癣蟋。” “怎么了吉嚣?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵梢薪,是天一觀的道長。 經(jīng)常有香客問我尝哆,道長秉撇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任秋泄,我火速辦了婚禮琐馆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恒序。我一直安慰自己瘦麸,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布歧胁。 她就那樣靜靜地躺著滋饲,像睡著了一般厉碟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上屠缭,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天箍鼓,我揣著相機與錄音,去河邊找鬼呵曹。 笑死款咖,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奄喂。 我是一名探鬼主播铐殃,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼跨新!你這毒婦竟也來了富腊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤域帐,失蹤者是張志新(化名)和其女友劉穎蟹肘,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俯树,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年贰盗,在試婚紗的時候發(fā)現(xiàn)自己被綠了许饿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡舵盈,死狀恐怖陋率,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秽晚,我是刑警寧澤瓦糟,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站赴蝇,受9級特大地震影響菩浙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜句伶,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一劲蜻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧考余,春花似錦先嬉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽含懊。三九已至,卻和暖如春衅胀,著一層夾襖步出監(jiān)牢的瞬間岔乔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工拗小, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留重罪,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓哀九,卻偏偏與公主長得像剿配,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子阅束,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354

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