Redis Configurations
现在你的应用程序配置完毕,你可能希望开始自定义某些内容:
Now that you have your application configured, you might want to start customizing things:
-
I want to {spring-boot-ref-docs}/application-properties.html#application-properties.data.spring.data.redis.host[customize the Redis configuration] using Spring Boot properties
-
I want choosing-between-regular-and-indexed
RedisSessionRepository
orRedisIndexedSessionRepository
. -
I want to serializing-session-using-json.
-
I want to using-a-different-namespace.
-
I want to deleted, destroyed or expires,listening-session-events.
-
I want to finding-all-user-sessions
-
I want to configuring-redis-session-mapper
Serializing the Session using JSON
默认情况下,Spring Session 使用 Java 序列化序列化会话属性。有时这可能会出现问题,特别是当使用同一 Redis 实例但具有相同类别的不同版本时。你可以提供 RedisSerializer
bean 来自定义会话序列化到 Redis 的方式。Spring Data Redis 提供 GenericJackson2JsonRedisSerializer
,它使用 Jackson’s ObjectMapper
序列化和反序列化对象。
By default, Spring Session uses Java Serialization to serialize the session attributes.
Sometimes it might be problematic, especially when you have multiple applications that use the same Redis instance but have different versions of the same class.
You can provide a RedisSerializer
bean to customize how the session is serialized into Redis.
Spring Data Redis provides the GenericJackson2JsonRedisSerializer
that serializes and deserializes objects using 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 并像这样使用它:
The above code snippet is using Spring Security, therefore we are creating a custom ObjectMapper
that uses Spring Security’s Jackson modules.
If you do not need Spring Security Jackson modules, you can inject your application’s ObjectMapper
bean and use it like so:
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(ObjectMapper objectMapper) {
return new GenericJackson2JsonRedisSerializer(objectMapper);
}
Specifying a Different Namespace
使用同一 Redis 实例有多个应用程序的情况并不少见。出于该原因,Spring Session 使用 namespace
(默认为 spring:session
)在需要时保持会话数据分离。
It is not uncommon to have multiple applications that use the same Redis instance.
For that reason, Spring Session uses a namespace
(defaults to spring:session
) to keep the session data separated if needed.
Using Spring Boot Properties
你可以通过设置 spring.session.redis.namespace
属性来指定它。
You can specify it by setting the spring.session.redis.namespace
property.
spring.session.redis.namespace=spring:session:myapplication
spring:
session:
redis:
namespace: "spring:session:myapplication"
Using the Annotation’s Attributes
你可以通过在 @EnableRedisHttpSession
、@EnableRedisIndexedHttpSession
或 @EnableRedisWebSession
注释中设置 redisNamespace
属性来指定 namespace
:
You can specify the namespace
by setting the redisNamespace
property in the @EnableRedisHttpSession
, @EnableRedisIndexedHttpSession
, or @EnableRedisWebSession
annotations:
@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
接口实现。但它们处理会话索引和查询的方式有所不同。
When working with Spring Session Redis, you will likely have to choose between the RedisSessionRepository
and the RedisIndexedSessionRepository
.
Both are implementations of the SessionRepository
interface that store session data in Redis.
However, they differ in how they handle session indexing and querying.
-
RedisSessionRepository
:RedisSessionRepository
is a basic implementation that stores session data in Redis without any additional indexing. It uses a simple key-value structure to store session attributes. Each session is assigned a unique session ID, and the session data is stored under a Redis key associated with that ID. When a session needs to be retrieved, the repository queries Redis using the session ID to fetch the associated session data. Since there is no indexing, querying sessions based on attributes or criteria other than the session ID can be inefficient. -
RedisIndexedSessionRepository
:RedisIndexedSessionRepository
is an extended implementation that provides indexing capabilities for sessions stored in Redis. It introduces additional data structures in Redis to efficiently query sessions based on attributes or criteria. In addition to the key-value structure used byRedisSessionRepository
, it maintains additional indexes to enable fast lookups. For example, it may create indexes based on session attributes like user ID or last access time. These indexes allow for efficient querying of sessions based on specific criteria, enhancing performance and enabling advanced session management features. In addition to that,RedisIndexedSessionRepository
also supports session expiration and deletion.
Configuring the RedisSessionRepository
Using Spring Boot Properties
如果你使用 Spring Boot,则 RedisSessionRepository
是默认实现。但是,如果你希望明确表达它,则可以在应用程序中设置以下属性:
If you are using Spring Boot, the RedisSessionRepository
is the default implementation.
However, if you want to be explicit about it, you can set the following property in your application:
spring.session.redis.repository-type=default
spring:
session:
redis:
repository-type: default
Configuring the RedisIndexedSessionRepository
Using Spring Boot Properties
你可以通过在应用程序中设置以下属性来配置 RedisIndexedSessionRepository
:
You can configure the RedisIndexedSessionRepository
by setting the following properties in your application:
spring.session.redis.repository-type=indexed
spring:
session:
redis:
repository-type: indexed
Listening to Session Events
通常,对会话事件做出反应很有价值,例如,你可能希望根据会话生命周期执行某种处理。为了做到这一点,你必须使用 indexed repository。如果你不知道索引存储库和默认存储库之间的区别,请转至 this section。
Often times it is valuable to react to session events, for example, you might want to do some kind of processing depending on the session lifecycle. In order to be able to do that, you must be using the configuring-redisindexedsessionrepository. If you do not know the difference between the indexed and the default repository, you can go to choosing-between-regular-and-indexed.
在配置了已编制索引的存储库后,现在可以开始监听 SessionCreatedEvent
、SessionDeletedEvent
、SessionDestroyedEvent
和 SessionExpiredEvent
事件。Spring 中有多种 {docs-url}/spring-framework/reference/core/beans/context-introduction.html#context-functionality-events[监听应用程序事件的方法],我们将使用 @EventListener
注释。
With the indexed repository configured, you can now start to listen to SessionCreatedEvent
, SessionDeletedEvent
, SessionDestroyedEvent
and SessionExpiredEvent
events.
There are a {docs-url}/spring-framework/reference/core/beans/context-introduction.html#context-functionality-events[few ways to listen to application events] in Spring, we are going to use the @EventListener
annotation.
@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
通过检索特定用户的全部会话,你可以在所有设备或浏览器中跟踪用户的活动会话。例如,你可以使用此信息用于会话管理目的,例如允许用户注销或从特定会话中退出,或根据用户的会话活动执行操作。
By retrieving all sessions of a specific user, you can track the user’s active sessions across devices or browsers. For example, you can use this information session management purposes, such as allowing the user to invalidate or logout from specific sessions or performing actions based on the user’s session activity.
要做到这一点,你首先必须使用 indexed repository,然后你可以注入 FindByIndexNameSessionRepository
接口,如下所示:
To do that, first you must be using the configuring-redisindexedsessionrepository, and then you can inject the FindByIndexNameSessionRepository
interface, like so:
@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
方法删除用户的特定会话。
In the example above, you can use the getSessions
method to find all sessions of a specific user, and the removeSession
method to remove a specific session of a user.
Configuring Redis Session Mapper
Spring Session Redis 从 Redis 中检索会话信息,并将其存储在 Map<String, Object>
中。该映射需要经过一个映射过程才能转换为 MapSession
对象,然后在 RedisSession
中使用。
Spring Session Redis retrieves session information from Redis and stores it in a Map<String, Object>
.
This map needs to undergo a mapping process to be transformed into a MapSession
object, which is then utilized within RedisSession
.
用于此目的的默认映射器被称为 RedisSessionMapper
.如果会话映射不包含构造会话所需的最少量键,如 creationTime
,此映射器将会抛出一个异常。必需键缺失的一个可能场景是当在保存进程进行中时,会话键被同时删除,通常由于过期。这发生是因为 HSET command 被用来设置键中的字段,如果键不存在,此命令将会创建它。
The default mapper used for this purpose is called RedisSessionMapper
.
If the session map doesn’t contain the minimum necessary keys to construct the session, like creationTime
, this mapper will throw an exception.
One possible scenario for the absence of required keys is when the session key is deleted concurrently, usually due to expiration, while the save process is in progress.
This occurs because the HSET command is employed to set fields within the key, and if the key doesn’t exist, this command will create it.
如果你想自定义映射过程,可以创建 BiFunction<String, Map<String, Object>, MapSession>
的实现并将其设置到会话存储库中。以下示例演示如何将映射过程委派给默认映射器,但是如果抛出异常,则会话将从 Redis 中删除:
If you want to customize the mapping process, you can create your implementation of BiFunction<String, Map<String, Object>, MapSession>
and set it into the session repository.
The following example shows how to delegate the mapping process to the default mapper, but if an exception is thrown, the session is deleted from 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()));
}
}
}