HttpSessionListener 與 HttpSessionAttributeListener

在寫上一篇《單用戶登錄》博文的時候,看了比較多關於 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 裏面的值,把這句註釋掉就正常了。
在這裏插入圖片描述通過上面的三種情況,相信各位朋友對這兩個監聽器的使用姿勢也有更進一步的瞭解,大概。有不正確、不明白的地方歡迎指出、討論哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章