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 里面的值,把这句注释掉就正常了。
在这里插入图片描述通过上面的三种情况,相信各位朋友对这两个监听器的使用姿势也有更进一步的了解,大概。有不正确、不明白的地方欢迎指出、讨论哦!

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