在寫上一篇《單用戶登錄》博文的時候,看了比較多關於 session 的文章,覺得 HttpSessionListener 與 HttpSessionAttributeListener 比較實用,就寫了本博文,看看這兩個監聽器的基本用法。
session 監聽器的最常用的就是統計在線用戶數了,HttpSessionListener 與 HttpSessionAttributeListener 這兩個監聽器都可以實現,詳細見代碼。
登錄頁和主頁都是 Bootstrap 中的例子
MySesssionListener.java(自定義 HttpSessionListener 監聽器)
public class MySesssionListener implements HttpSessionListener{
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("sessionCreated===============");
HttpSession session = se.getSession();
// 把已登錄的用戶信息map 放在 application 域中
ServletContext application = session.getServletContext();
if(session.isNew()) {
String userEntity = (String) session.getAttribute("username_session");
HashMap<String, String> onlineUsers = (HashMap<String, String>) application.getAttribute("onlineUsers");
if(onlineUsers == null) {
onlineUsers = new HashMap<>();
}
onlineUsers.put(session.getId(), userEntity);
application.setAttribute("onlineUsers", onlineUsers);
}
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("sessionDestroyed===============");
HttpSession session = se.getSession();
ServletContext application = session.getServletContext();
HashMap<String, String> onlineUsers = (HashMap<String, String>) application.getAttribute("onlineUsers");
if(onlineUsers != null) {
onlineUsers.remove(session.getId());
}
application.setAttribute("onlineUsers", onlineUsers);
}
}
MySesssionAttributeListener.java(自定義 HttpSessionAttributeListener 監聽器)
public class MySesssionAttributeListener implements HttpSessionAttributeListener{
// 靜態map,存儲已登錄的用戶信息
private static Map<String, String> onlineMap = new HashMap<String, String>();
@Override
public synchronized void attributeAdded(HttpSessionBindingEvent se) {
System.out.println("attributeAdded===================");
if("username_session".equals(se.getName())) {
//這個值是在登錄後放入用戶session的值,在實際項目中應該是用戶對象
String userEntity = (String) se.getValue();
onlineMap.put(userEntity, userEntity);
}
}
@Override
public synchronized void attributeRemoved(HttpSessionBindingEvent se) {
System.out.println("attributeRemoved===================");
if ("username_session".equals(se.getName())) {
String userEntity = (String) se.getValue();
onlineMap.remove(userEntity);
}
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
System.out.println("attributeReplaced===================");
HttpSessionAttributeListener.super.attributeReplaced(se);
}
// 返回已登錄用戶的列表
public synchronized static List<String> getOnlineMap() {
List<String> list = new ArrayList<String>();
Set<String> set = onlineMap.keySet();
for (String uname : set) {
list.add(onlineMap.get(uname));
}
return list;
}
}
配置類 Myconfig.java
配置類中,我同時把 HttpSessionListener 與 HttpSessionAttributeListener 的監聽器都註冊到容器中了。注意不要在同一個 bean 中 set 兩個監聽器,這樣後面的覆蓋前面的。
@Configuration
public class Myconfig extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
// 綁定靜態資源
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/","classpath:/public/");
super.addResourceHandlers(registry);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public ServletListenerRegistrationBean myListener1(){
ServletListenerRegistrationBean lrb = new ServletListenerRegistrationBean();
lrb.setListener(new MySesssionListener());
return lrb;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public ServletListenerRegistrationBean myListener2(){
ServletListenerRegistrationBean lrb = new ServletListenerRegistrationBean();
lrb.setListener(new MySesssionAttributeListener());
return lrb;
}
}
DemoTwoController.java
在這個控制類中,我把輸出在線人數的語句放到登出的請求中了。(便於測試 ^ . ^)
@Controller
public class DemoTwoController {
@RequestMapping("/index")
public String index() {
return "index";
}
@RequestMapping("/home")
public String dashboard() {
return "dashboard";
}
@PostMapping("/login")
public String login(@RequestParam String uname, @RequestParam String pwd,
Map<String, Object>map, HttpSession session) {
if(uname.equals("xiao") || uname.equals("admin")) {
System.out.println("開始設置 session 屬性");
//把當前用戶放到session中
session.setAttribute("username_session", uname);
session.setAttribute("username_session", "testAttributeReplaced");
System.out.println("設置 session 屬性完成");
return "redirect:home";
}else {
map.put("failMsg", "用戶名或密碼錯誤");
return "index";
}
}
@RequestMapping("/signout")
public String signout(HttpSession session) {
ServletContext application = session.getServletContext();
HashMap<String, String> onlineUsers = (HashMap<String, String>) application.getAttribute("onlineUsers");
System.out.println("系統當前在線人數(application中map)====== " + onlineUsers.size());
Set<String> sidSet = onlineUsers.keySet();
for (String sessionid : sidSet) {
System.out.println(onlineUsers.get(sessionid));
}
List<String> onlineMap = MySesssionAttributeListener.getOnlineMap();
System.out.println("系統當前在線人數(靜態map)====== " + onlineMap.size());
for(String uname: onlineMap) {
System.out.println(uname);
}
System.out.println("開始清除 session 屬性");
//把當前用戶放到session中
session.removeAttribute("username_session");
System.out.println("清除 session 屬性完成");
session.invalidate();
System.out.println("session.invalidate()完成");
return "index";
}
}
啓動項目
1)在 Chrome 瀏覽器登錄 xiao 賬號
2)在火狐瀏覽器登錄 xiao 賬號
3)在 Chrome 瀏覽器登出
4)在火狐瀏覽器登出
控制檯輸出
sessionCreated===============
開始設置 session 屬性
attributeAdded===================
attributeReplaced===================
設置 session 屬性完成
2020-01-15 20:18:56.410 WARN 7156 --- [nio-8082-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 20:18:56.427 WARN 7156 --- [nio-8082-exec-9] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 20:18:56.575 WARN 7156 --- [nio-8082-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
sessionCreated===============
開始設置 session 屬性
attributeAdded===================
attributeReplaced===================
設置 session 屬性完成
2020-01-15 20:19:20.517 WARN 7156 --- [nio-8082-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 20:19:20.530 WARN 7156 --- [io-8082-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 20:19:20.705 WARN 7156 --- [nio-8082-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 20:19:20.750 WARN 7156 --- [nio-8082-exec-9] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
系統當前在線人數(application中map)====== 2
null
null
系統當前在線人數(靜態map)====== 1
xiao
開始清除 session 屬性
attributeRemoved===================
清除 session 屬性完成
sessionDestroyed===============
session.invalidate()完成
系統當前在線人數(application中map)====== 1
null
系統當前在線人數(靜態map)====== 1
xiao
開始清除 session 屬性
attributeRemoved===================
清除 session 屬性完成
sessionDestroyed===============
session.invalidate()完成
可以發現
1)HttpSessionListener 中的 map 和 HttpSessionAttributeListener 中的 map 存儲的在線人數是不同的,儘管在兩個不同的瀏覽器登錄了同一個賬號,HttpSessionListener 中的 map 是統計了兩次,而 HttpSessionAttributeListener 中的 map 只算了一次。
2)HttpSessionListener 中的 map 沒有保存到在線用戶的信息,爲 null,因爲這個監聽器是監聽 session 創建的,剛創建就被監聽,然後調用 sessionCreated() 方法,這個時候 session 中還沒設置用戶信息呢。而 HttpSessionAttributeListener 就可以,attributeAdded() 方法是監聽到 session 屬性添加後被調用的方法,在這裏可以獲取設置的用戶信息。
再試試這種情況:
1)在 Chrome 瀏覽器登錄 xiao 賬號
2)在 Chrome 瀏覽器另開一個標籤頁登錄 admin 賬號
3)分別在兩個標籤頁登出
sessionCreated===============
開始設置 session 屬性
attributeAdded===================
attributeReplaced===================
設置 session 屬性完成
2020-01-15 20:21:49.779 WARN 7156 --- [nio-8082-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 20:21:49.789 WARN 7156 --- [nio-8082-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
開始設置 session 屬性
attributeReplaced===================
attributeReplaced===================
設置 session 屬性完成
2020-01-15 20:22:16.208 WARN 7156 --- [nio-8082-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 20:22:16.223 WARN 7156 --- [nio-8082-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 20:22:16.323 WARN 7156 --- [io-8082-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 20:22:16.381 WARN 7156 --- [nio-8082-exec-9] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
系統當前在線人數(application中map)====== 1
null
系統當前在線人數(靜態map)====== 1
xiao
開始清除 session 屬性
attributeRemoved===================
清除 session 屬性完成
sessionDestroyed===============
session.invalidate()完成
sessionCreated===============
系統當前在線人數(application中map)====== 1
null
系統當前在線人數(靜態map)====== 1
xiao
開始清除 session 屬性
清除 session 屬性完成
sessionDestroyed===============
session.invalidate()完成
這個輸出有點奇怪,仔細看信息
1)由於是同一個瀏覽器內,所以算一次會話,HttpSessionListener 第一次輸出在線人數是 1 可以理解,HttpSessionAttributeListener 這裏用戶名應該是設置過兩次的,不應該是 2 嗎?原來,一次會話中,再次設置用戶 session 就是調用 attributeReplaced() 了,看輸出信息,第一個賬號登錄時 attributeReplaced() 調用了一次,而另一個標籤頁登錄第二個用戶時 attributeReplaced() 調用了2次。也就是說設置第二個用戶session就已經算是在修改 session 屬性了,而不是在新增第二個用戶 session。
2)第二個標籤頁登出時,在線人數也是都顯示 1,第一次都是顯示 1 ,已經退出一個了,不應該顯示 0 嗎?再查看輸出信息,發現第一次session.invalidate()完成後又調用了一次sessionCreated。也就是說在第一個標籤登出後,切換到第二個標籤時,相當於刷新了第二個標籤頁的主頁,又重新建立了一次會話。但是這裏應該沒有驗證賬戶,也就沒有設置用戶 session,沒有屬性變化,看輸出信息也沒有調用屬性增加的方法,HttpSessionAttributeListener 的在線用戶數應該爲 0 纔對。
3)通過 debug 發現,第一個標籤頁登出時,執行了 attributeRemoved() 方法,第二個標籤頁登出時沒有執行這個方法了,所以 HttpSessionAttributeListener 中 map 的值沒有刪除,看輸出信息也能看出第二次確實沒有執行 attributeRemoved() 方法
還有這種情況:
1)在 Chrome 瀏覽器登錄 xiao 賬號
2)在火狐瀏覽器登錄 admin 賬號
3)在 Chrome 瀏覽器登出
4)在火狐瀏覽器登出
sessionCreated===============
開始設置 session 屬性
attributeAdded===================
attributeReplaced===================
設置 session 屬性完成
2020-01-15 21:09:14.573 WARN 7452 --- [nio-8082-exec-9] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 21:09:14.581 WARN 7452 --- [nio-8082-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 21:09:14.676 WARN 7452 --- [io-8082-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 21:09:14.710 WARN 7452 --- [nio-8082-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
sessionCreated===============
開始設置 session 屬性
attributeAdded===================
attributeReplaced===================
設置 session 屬性完成
2020-01-15 21:09:20.126 WARN 7452 --- [nio-8082-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 21:09:20.136 WARN 7452 --- [nio-8082-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 21:09:20.305 WARN 7452 --- [nio-8082-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
2020-01-15 21:09:20.370 WARN 7452 --- [nio-8082-exec-7] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.LinkedHashMap]
系統當前在線人數(application中map)====== 2
null
null
系統當前在線人數(靜態map)====== 2
xiao
admin
開始清除 session 屬性
attributeRemoved===================
清除 session 屬性完成
sessionDestroyed===============
session.invalidate()完成
系統當前在線人數(application中map)====== 1
null
系統當前在線人數(靜態map)====== 2
xiao
admin
開始清除 session 屬性
attributeRemoved===================
清除 session 屬性完成
sessionDestroyed===============
session.invalidate()完成
從控制檯的輸出中看到 HttpSessionAttributeListener 的 map 統計的在線人數居然兩次登出都是 2,這裏明顯有問題啊。通過 debug ,我發現原來是我在登錄驗證後設置 session 時,爲了查看 attributeReplaced() 方法的調用時機,又多寫了下面這句,導致在刪除 session 時沒有刪除掉 map 裏面的值,把這句註釋掉就正常了。
通過上面的三種情況,相信各位朋友對這兩個監聽器的使用姿勢也有更進一步的瞭解,大概。有不正確、不明白的地方歡迎指出、討論哦!