目录
结论
废话后边说。先上结论
spring boot依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<relativePath/>
</parent>
spring session依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
session事件监听代码
@Component
public class SessionEventListener {
@EventListener
public void handleCreated(SessionCreatedEvent event) {
System.out.println("Session created: " + event.getClass().getName() + " - " + event.getSessionId());
}
@EventListener
public void handleDestroyed(SessionDestroyedEvent event) {
System.out.println("Session destroyed: " + event.getClass().getName() + " - " + event.getSessionId());
}
@EventListener
public void handleExpired(SessionExpiredEvent event) {
System.out.println("Session expired: " + event.getClass().getName() + " - " + event.getSessionId());
}
}
解决方案
application.yml
里spring:session:redis
下增加 repository-type: indexed
spring:
session:
timeout: 864000 # 默认10天
redis:
namespace: xxxxx:session
repository-type: indexed # 增加这句
源码分析
话不多说,直接看重点。。。RedisSessionConfiguration
类中可以看到为什么要修改默认配置
repository-type
为default
时创建RedisSessionRepository
repository-type
为indexed
时创建RedisIndexedSessionRepository
RedisSessionRepository
/**
* A {@link SessionRepository} implementation that uses Spring Data's
* {@link RedisOperations} to store sessions is Redis.
* <p>
* This implementation does not support publishing of session events.
*
* @author Vedran Pavic
* @since 2.2.0
*/
public class RedisSessionRepository implements SessionRepository<RedisSessionRepository.RedisSession> {}
RedisIndexedSessionRepository
@Override
public void onMessage(Message message, byte[] pattern) {
byte[] messageChannel = message.getChannel();
if (ByteUtils.startsWith(messageChannel, this.sessionCreatedChannelPrefixBytes)) {
// TODO: is this thread safe?
String channel = new String(messageChannel);
String sessionId = channel.substring(channel.lastIndexOf(":") + 1);
@SuppressWarnings("unchecked")
Map<String, Object> entries = (Map<String, Object>) this.defaultSerializer.deserialize(message.getBody());
MapSession loaded = this.redisSessionMapper.apply(sessionId, entries);
if (loaded != null) {
RedisSession session = new RedisSession(loaded, false);
handleCreated(session);
}
return;
}
byte[] messageBody = message.getBody();
if (!ByteUtils.startsWith(messageBody, this.expiredKeyPrefixBytes)) {
return;
}
boolean isDeleted = Arrays.equals(messageChannel, this.sessionDeletedChannelBytes);
if (isDeleted || Arrays.equals(messageChannel, this.sessionExpiredChannelBytes)) {
String body = new String(messageBody);
int beginIndex = body.lastIndexOf(":") + 1;
int endIndex = body.length();
String sessionId = body.substring(beginIndex, endIndex);
RedisSession session = getSession(sessionId, true);
if (session == null) {
logger.warn("Unable to publish SessionDestroyedEvent for session " + sessionId);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Publishing SessionDestroyedEvent for session " + sessionId);
}
cleanupPrincipalIndex(session);
if (isDeleted) {
handleDeleted(session);
}
else {
handleExpired(session);
}
}
}
其中handleCreated
、handleDeleted
、handleExpired
方法是发布session事件,以下源码
private void handleCreated(RedisSession session) {
publishEvent(new SessionCreatedEvent(this, session));
}
private void handleDeleted(RedisSession session) {
publishEvent(new SessionDeletedEvent(this, session));
}
private void handleExpired(RedisSession session) {
publishEvent(new SessionExpiredEvent(this, session));
}
private void publishEvent(ApplicationEvent event) {
try {
this.eventPublisher.publishEvent(event);
}
catch (Throwable ex) {
logger.error("Error publishing " + event + ".", ex);
}
}
总结
- spring-session-redis 默认实现为
RedisSessionRepository
,不支持发布session事件 - 改配置文件(
spring:session:redis
下增加repository-type: indexed
)后为RedisIndexedSessionRepository
实现,此时支持session事件,监听生效