Redis Configurations
现在你的应用程序配置完毕,你可能希望开始自定义某些内容:
-
我想使用 Spring Boot 属性 {spring-boot-ref-docs}/application-properties.html#application-properties.data.spring.data.redis.host[定制 Redis 配置]
-
我想
RedisSessionRepository
orRedisIndexedSessionRepository
help in choosing。 -
我想 destroyed or expires,listening-session-events,know when a session is created。
Serializing the Session using JSON
默认情况下,Spring Session 使用 Java 序列化序列化会话属性。有时这可能会出现问题,特别是当使用同一 Redis 实例但具有相同类别的不同版本时。你可以提供 RedisSerializer
bean 来自定义会话序列化到 Redis 的方式。Spring Data Redis 提供 GenericJackson2JsonRedisSerializer
,它使用 Jackson’s ObjectMapper
序列化和反序列化对象。
Unresolved include directive in modules/ROOT/pages/configuration/redis.adoc - include::example$spring-session-samples/spring-session-sample-boot-redis-json/src/main/java/sample/config/SessionConfig.java[]
上面的代码片段使用的是 Spring Security,因此我们正在创建一个自定义 ObjectMapper
,它使用 Spring Security 的 Jackson 模块。如果您不需要 Spring Security Jackson 模块,则可以注入应用程序的 ObjectMapper
bean 并像这样使用它:
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(ObjectMapper objectMapper) {
return new GenericJackson2JsonRedisSerializer(objectMapper);
}
Specifying a Different Namespace
使用同一 Redis 实例有多个应用程序的情况并不少见。出于该原因,Spring Session 使用 namespace
(默认为 spring:session
)在需要时保持会话数据分离。
Using Spring Boot Properties
你可以通过设置 spring.session.redis.namespace
属性来指定它。
spring.session.redis.namespace=spring:session:myapplication
spring:
session:
redis:
namespace: "spring:session:myapplication"
Using the Annotation’s Attributes
你可以通过在 @EnableRedisHttpSession
、@EnableRedisIndexedHttpSession
或 @EnableRedisWebSession
注释中设置 redisNamespace
属性来指定 namespace
:
@Configuration
@EnableRedisHttpSession(redisNamespace = "spring:session:myapplication")
public class SessionConfig {
// ...
}
@Configuration
@EnableRedisIndexedHttpSession(redisNamespace = "spring:session:myapplication")
public class SessionConfig {
// ...
}
@Configuration
@EnableRedisWebSession(redisNamespace = "spring:session:myapplication")
public class SessionConfig {
// ...
}
Choosing Between RedisSessionRepository
and RedisIndexedSessionRepository
在使用 Spring Session Redis 时,你可能不得不从 RedisSessionRepository
和 RedisIndexedSessionRepository
中进行选择。二者都是将会话数据存储在 Redis 中的 SessionRepository
接口实现。但它们处理会话索引和查询的方式有所不同。
-
RedisSessionRepository
:RedisSessionRepository
是一个基本实现,它在 Redis 中存储会话数据,没有任何其他索引。它使用简单的键值结构存储会话属性。为每个会话分配一个唯一的会话 ID,会话数据存储在与该 ID 关联的 Redis 密钥下。当需要检索会话时,存储库使用会话 ID 查询 Redis 以获取关联的会话数据。由于没有索引,基于属性或非会话 ID 标准查询会话可能会很低效。 -
RedisIndexedSessionRepository
:RedisIndexedSessionRepository
是一个扩展的实现,它为存储在 Redis 中的会话提供索引功能。它在 Redis 中引入其他数据结构,以便基于属性或条件有效地查询会话。除了RedisSessionRepository
使用的键值结构之外,它还维护其他索引以启用快速查找。例如,它可以根据会话属性(如用户 ID 或上次访问时间)创建索引。这些索引允许基于特定条件有效地查询会话,从而提高性能并启用高级会话管理功能。除此之外,RedisIndexedSessionRepository
还支持会话过期和删除。
Configuring the RedisSessionRepository
Listening to Session Events
通常,对会话事件做出反应很有价值,例如,你可能希望根据会话生命周期执行某种处理。为了做到这一点,你必须使用 indexed repository。如果你不知道索引存储库和默认存储库之间的区别,请转至 this section。
在配置了已编制索引的存储库后,现在可以开始监听 SessionCreatedEvent
、SessionDeletedEvent
、SessionDestroyedEvent
和 SessionExpiredEvent
事件。Spring 中有多种 {docs-url}/spring-framework/reference/core/beans/context-introduction.html#context-functionality-events[监听应用程序事件的方法],我们将使用 @EventListener
注释。
@Component
public class SessionEventListener {
@EventListener
public void processSessionCreatedEvent(SessionCreatedEvent event) {
// do the necessary work
}
@EventListener
public void processSessionDeletedEvent(SessionDeletedEvent event) {
// do the necessary work
}
@EventListener
public void processSessionDestroyedEvent(SessionDestroyedEvent event) {
// do the necessary work
}
@EventListener
public void processSessionExpiredEvent(SessionExpiredEvent event) {
// do the necessary work
}
}
Finding All Sessions of a Specific User
通过检索特定用户的全部会话,你可以在所有设备或浏览器中跟踪用户的活动会话。例如,你可以使用此信息用于会话管理目的,例如允许用户注销或从特定会话中退出,或根据用户的会话活动执行操作。
要做到这一点,你首先必须使用 indexed repository,然后你可以注入 FindByIndexNameSessionRepository
接口,如下所示:
@Autowired
public FindByIndexNameSessionRepository<? extends Session> sessions;
public Collection<? extends Session> getSessions(Principal principal) {
Collection<? extends Session> usersSessions = this.sessions.findByPrincipalName(principal.getName()).values();
return usersSessions;
}
public void removeSession(Principal principal, String sessionIdToDelete) {
Set<String> usersSessionIds = this.sessions.findByPrincipalName(principal.getName()).keySet();
if (usersSessionIds.contains(sessionIdToDelete)) {
this.sessions.deleteById(sessionIdToDelete);
}
}
在以上示例中,你可以使用 getSessions
方法查找特定用户的全部会话,并使用 removeSession
方法删除用户的特定会话。
Configuring Redis Session Mapper
Spring Session Redis 从 Redis 中检索会话信息,并将其存储在 Map<String, Object>
中。该映射需要经过一个映射过程才能转换为 MapSession
对象,然后在 RedisSession
中使用。
用于此目的的默认映射器被称为 RedisSessionMapper
.如果会话映射不包含构造会话所需的最少量键,如 creationTime
,此映射器将会抛出一个异常。必需键缺失的一个可能场景是当在保存进程进行中时,会话键被同时删除,通常由于过期。这发生是因为 HSET command 被用来设置键中的字段,如果键不存在,此命令将会创建它。
如果你想自定义映射过程,可以创建 BiFunction<String, Map<String, Object>, MapSession>
的实现并将其设置到会话存储库中。以下示例演示如何将映射过程委派给默认映射器,但是如果抛出异常,则会话将从 Redis 中删除:
-
RedisSessionRepository
-
RedisIndexedSessionRepository
-
ReactiveRedisSessionRepository
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
@Bean
SessionRepositoryCustomizer<RedisSessionRepository> redisSessionRepositoryCustomizer() {
return (redisSessionRepository) -> redisSessionRepository
.setRedisSessionMapper(new SafeRedisSessionMapper(redisSessionRepository));
}
static class SafeRedisSessionMapper implements BiFunction<String, Map<String, Object>, MapSession> {
private final RedisSessionMapper delegate = new RedisSessionMapper();
private final RedisSessionRepository sessionRepository;
SafeRedisSessionMapper(RedisSessionRepository sessionRepository) {
this.sessionRepository = sessionRepository;
}
@Override
public MapSession apply(String sessionId, Map<String, Object> map) {
try {
return this.delegate.apply(sessionId, map);
}
catch (IllegalStateException ex) {
this.sessionRepository.deleteById(sessionId);
return null;
}
}
}
}
@Configuration
@EnableRedisIndexedHttpSession
public class SessionConfig {
@Bean
SessionRepositoryCustomizer<RedisIndexedSessionRepository> redisSessionRepositoryCustomizer() {
return (redisSessionRepository) -> redisSessionRepository.setRedisSessionMapper(
new SafeRedisSessionMapper(redisSessionRepository.getSessionRedisOperations()));
}
static class SafeRedisSessionMapper implements BiFunction<String, Map<String, Object>, MapSession> {
private final RedisSessionMapper delegate = new RedisSessionMapper();
private final RedisOperations<String, Object> redisOperations;
SafeRedisSessionMapper(RedisOperations<String, Object> redisOperations) {
this.redisOperations = redisOperations;
}
@Override
public MapSession apply(String sessionId, Map<String, Object> map) {
try {
return this.delegate.apply(sessionId, map);
}
catch (IllegalStateException ex) {
// if you use a different redis namespace, change the key accordingly
this.redisOperations.delete("spring:session:sessions:" + sessionId); // we do not invoke RedisIndexedSessionRepository#deleteById to avoid an infinite loop because the method also invokes this mapper
return null;
}
}
}
}
@Configuration
@EnableRedisWebSession
public class SessionConfig {
@Bean
ReactiveSessionRepositoryCustomizer<ReactiveRedisSessionRepository> redisSessionRepositoryCustomizer() {
return (redisSessionRepository) -> redisSessionRepository
.setRedisSessionMapper(new SafeRedisSessionMapper(redisSessionRepository));
}
static class SafeRedisSessionMapper implements BiFunction<String, Map<String, Object>, Mono<MapSession>> {
private final RedisSessionMapper delegate = new RedisSessionMapper();
private final ReactiveRedisSessionRepository sessionRepository;
SafeRedisSessionMapper(ReactiveRedisSessionRepository sessionRepository) {
this.sessionRepository = sessionRepository;
}
@Override
public Mono<MapSession> apply(String sessionId, Map<String, Object> map) {
return Mono.fromSupplier(() -> this.delegate.apply(sessionId, map))
.onErrorResume(IllegalStateException.class,
(ex) -> this.sessionRepository.deleteById(sessionId).then(Mono.empty()));
}
}
}