spring-session解决session创建销毁事件失效的问题
背景
项目中使用了spring-session作为分布式session,session存储采用了sprign-session-jdbc。
同时使用了spring-security作为安全框架
配置如下:
1 | /** |
1 |
|
现在有一个需求是统计在线人数,于是想到了通过统计session来获取人数。
根据一般的方法,只需要在spring-security中添加事件发布,然后再添加一个自定义的监听器即可
spring-security的配置中添加session的Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 加入监听器,session销毁时才会触发 spring容器的感知,否则 security监听不到销毁
* @return
*/
HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
//其余spring-security配置省略
}创建一个自定义的监听器,监听session事件
1 | //记录session存储的容器,可以从这里拿到session数量 |
从理论上来说这样就完成了session事件的监听
这时候问题来了,经测试,这个方法压根就没有调用到,也就是没有监听到发布的事件。
解决方案
先直接说解决方案
添加一个切面,增强SessionRepository
这个接口的createSession
和deleteById
方法,并发布事件
1 |
|
注意:记得加入到spring容器中必须要在容器里才能生效这不用多说
再来说一下原理
其原因是spring-session-jdb采用了JdbcIndexedSessionRepository
这个类来处理session的创建查询等,但是这里并没有提供session的事件发布,上面的切面类就是增强,在创建和删除的方法调用时发布事件SessionDestroyedEvent
和SessionDestroyedEvent
,这样应用中监听的HttpSessionListener
就能获取到数据了
spring-session-redis也是一样的原理,我们增强也就可以直接增强SessionRepository
这个接口了,把接口增强就可以兼容多种
问题排查
既然是没有监听到,那么首先要看一下是否有发布事件但是没有监听事件,这里最主要的就是检查监听器有没有放入容器中,有没有实现
HttpSessionListener
,有没有添加@WebListener
加入到监听器中,这很好排查。所以肯定不是这里的问题再看是不是
HttpSessionEvent
事件没有发布呢。根据方法,找到了HttpSessionEventPublisher
这个,这很明显就是发布这个事件的了。
再往上找到调用这个事件的类SessionEventHttpSessionListenerAdapter
,这个也是一个监听器,在onApplicationEvent()
方法循环调用字段List<HttpSessionListener> listeners
也就是spring中的监听器因此这里需要检查的是SessionEventHttpSessionListenerAdapter
有没有配置,HttpSessionEventPublisher
这个监听器有没有在listeners
中,
再通过SessionEventHttpSessionListenerAdapter
构造器往上找,查看创建这个类的配置SpringHttpSessionConfiguration
。经过debug,SpringHttpSessionConfiguration
是创建并配置了的,SessionEventHttpSessionListenerAdapter
也是加入到容器中的,HttpSessionEventPublisher
也加入到了SessionEventHttpSessionListenerAdapter
的字段listeners
中。因此这里也是没有问题的以上都没有问题,那就说明了
SessionEventHttpSessionListenerAdapter
监听器并没有监听到事件,因此onApplicationEvent(AbstractSessionEvent event)
方法没有执行到,所以自己定义的就没有监听到,因此需要查找AbstractSessionEvent
这个事件没有发布的原因。AbstractSessionEvent
是个抽象类,查找其子类直观的可以看到有SessionCreatedEvent
,SessionDeletedEvent
,SessionDestroyedEvent
,SessionExpiredEvent
这4个子类,我们选取一个SessionCreatedEvent
来进行分析。从
SessionCreatedEvent
类查找哪些引用了SessionCreatedEvent
,可以惊奇的发现没有任何地方创建了这个类,因此可以判断此事件压根就没有发布,因此监听器没有监听到。虽然没有地方使用,但是我们可以找到EnableSpringHttpSession
这个注解对其的引用说明1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/*
* ......上面的省略
* <li>SessionEventHttpSessionListenerAdapter - is responsible for translating Spring
* Session events into HttpSessionEvent. In order for it to work, the implementation of
* SessionRepository you provide must support {@link SessionCreatedEvent} and
* {@link SessionDestroyedEvent}.</li>
* <li>
* </ul>
*
* @author Rob Winch
* @since 1.1
*/
public EnableSpringHttpSession {
}这个类很明确的说明了
SessionRepository
应该要支持SessionCreatedEvent
和SessionDestroyedEvent
再看我们的
spring-session-jdbc
使用的为JdbcIndexedSessionRepository
,而这个类并没有支持SessionCreatedEvent
和SessionDestroyedEvent
事件的发布,因此终于定位到了问题所在,因此只需要对JdbcIndexedSessionRepository
的接口SessionRepository
创建session和删除session时做增强即可。一般可以使用装饰器模式用一个类包装,或者使用动态代理切面增强,这里使用动态代理切面增强。
至此,就解决了session创建销毁拿不到的问题