因為每次業(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)