跳到主要内容

API 文档

QWen Max 中英对照 API Documentation

您可以在线浏览完整的Javadoc。关键 API 在以下章节中描述:

使用 Session

Session 是一个简化的名称值对的 Map

典型的用法可能如下所示:

class RepositoryDemo<S extends Session> {

private SessionRepository<S> repository; 1

void demo() {
S toSave = this.repository.createSession(); 2

3
User rwinch = new User("rwinch");
toSave.setAttribute(ATTR_USER, rwinch);

this.repository.save(toSave); 4

S session = this.repository.findById(toSave.getId()); 5

6
User user = session.getAttribute(ATTR_USER);
assertThat(user).isEqualTo(rwinch);
}

// ... setter methods ...

}
java
  • 我们创建一个 SessionRepository 实例,其泛型 S 继承自 Session。泛型类型在我们的类中定义。

  • 我们使用 SessionRepository 创建一个新的 Session,并将其赋值给类型为 S 的变量。

  • 我们与 Session 进行交互。在示例中,我们演示了将 User 保存到 Session 中。

  • 现在我们保存 Session。这就是为什么我们需要泛型 SSessionRepository 只允许保存通过相同 SessionRepository 创建或检索的 Session 实例。这使得 SessionRepository 能够进行特定于实现的优化(例如,仅写入已更改的属性)。

  • 我们从 SessionRepository 中检索 Session

  • 我们从 Session 中获取持久化的 User,而无需显式地将属性进行类型转换。

Session API 还提供了与 Session 实例过期相关的属性。

典型的用法可能如下所示:

class ExpiringRepositoryDemo<S extends Session> {

private SessionRepository<S> repository; 1

void demo() {
S toSave = this.repository.createSession(); 2
// ...
toSave.setMaxInactiveInterval(Duration.ofSeconds(30)); 3

this.repository.save(toSave); 4

S session = this.repository.findById(toSave.getId()); 5
// ...
}

// ... setter methods ...

}
java
  • 我们创建一个 SessionRepository 实例,其泛型类型 S 继承自 Session。泛型类型在我们的类中定义。

  • 我们使用 SessionRepository 创建一个新的 Session,并将其赋值给类型为 S 的变量。

  • 我们与 Session 进行交互。在示例中,我们展示了更新 Session 在过期前可以处于非活动状态的时间。

  • 现在我们保存 Session。这就是为什么我们需要泛型类型 SSessionRepository 只允许保存使用相同 SessionRepository 创建或检索的 Session 实例。这使得 SessionRepository 可以进行特定于实现的优化(例如,仅写入已更改的属性)。保存 Session 时会自动更新最后访问时间。

  • 我们从 SessionRepository 中检索 Session。如果 Session 已过期,结果将为空。

使用 SessionRepository

SessionRepository 负责创建、检索和持久化 Session 实例。

如果可能,你不应该直接与 SessionRepositorySession 进行交互。相反,开发者应该优先通过 HttpSessionWebSocket 集成间接地与 SessionRepositorySession 进行交互。

使用 FindByIndexNameSessionRepository

Spring Session 最基本的使用 Session 的 API 是 SessionRepository。这个 API 有意设计得非常简单,以便您可以轻松提供具有基本功能的其他实现。

一些 SessionRepository 实现也可以选择实现 FindByIndexNameSessionRepository。例如,Spring 的 Redis、JDBC 和 Hazelcast 支持库都实现了 FindByIndexNameSessionRepository

FindByIndexNameSessionRepository 提供了一种方法,用于查找具有给定索引名称和索引值的所有会话。作为一个由所有提供的 FindByIndexNameSessionRepository 实现支持的常见用例,您可以使用一种便捷的方法来查找特定用户的所有会话。这可以通过确保填充了名为 FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME 的会话属性,并将其设置为用户名来实现。由于 Spring Session 不知道正在使用的身份验证机制,因此您有责任确保该属性被填充。下面的示例展示了如何使用此功能:

String username = "username";
this.session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
java
备注

某些 FindByIndexNameSessionRepository 的实现提供了挂钩,以自动索引其他会话属性。例如,许多实现会自动确保当前的 Spring Security 用户名被索引到 FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME 索引名称下。

一旦会话被索引,你可以使用类似于如下的代码来查找:

String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository.findByPrincipalName(username);
java

使用 ReactiveSessionRepository

ReactiveSessionRepository 负责以非阻塞和响应式的方式创建、检索和持久化 Session 实例。

如果可能,你不应该直接与 ReactiveSessionRepositorySession 交互。相反,你应该优先通过 WebSession 集成间接地与 ReactiveSessionRepositorySession 交互。

使用 @EnableSpringHttpSession

您可以在 @Configuration 类中添加 @EnableSpringHttpSession 注解,以将 SessionRepositoryFilter 作为名为 springSessionRepositoryFilter 的 bean 暴露出来。为了使用该注解,您必须提供一个 SessionRepository bean。以下示例展示了如何实现这一点:

@EnableSpringHttpSession
@Configuration
public class SpringHttpSessionConfig {

@Bean
public MapSessionRepository sessionRepository() {
return new MapSessionRepository(new ConcurrentHashMap<>());
}

}
java

请注意,没有为你配置会话过期的基础设施。这是因为诸如会话过期之类的事情高度依赖于具体实现。这意味着,如果你需要清理过期的会话,你需要负责清理这些过期的会话。

使用 @EnableSpringWebSession

你可以在 @Configuration 类中添加 @EnableSpringWebSession 注解,以将 WebSessionManager 作为名为 webSessionManager 的 bean 暴露出来。要使用该注解,你必须提供一个 ReactiveSessionRepository bean。以下示例展示了如何实现这一点:

@Configuration(proxyBeanMethods = false)
@EnableSpringWebSession
public class SpringWebSessionConfig {

@Bean
public ReactiveSessionRepository reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
}

}
java

请注意,没有为您配置会话过期的基础设施。这是因为诸如会话过期之类的事情高度依赖于具体实现。这意味着,如果您需要清理过期的会话,您需要负责清理这些过期的会话。

使用 RedisSessionRepository

RedisSessionRepository 是一个使用 Spring Data 的 RedisOperations 实现的 SessionRepository。在 Web 环境中,这通常与 SessionRepositoryFilter 结合使用。请注意,此实现不支持会话事件的发布。

实例化 RedisSessionRepository

你可以从以下代码清单中看到一个典型的创建新实例的例子:

RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

// ... configure redisTemplate ...

SessionRepository<? extends Session> repository = new RedisSessionRepository(redisTemplate);
java

有关如何创建 RedisConnectionFactory 的更多信息,请参阅 Spring Data Redis 参考文档。

使用 @EnableRedisHttpSession

在Web环境中,创建新的 RedisSessionRepository 的最简单方法是使用 @EnableRedisHttpSession。你可以在 示例和指南(从这里开始) 中找到完整的示例用法。你可以使用以下属性来自定义配置:

enableIndexingAndEvents * enableIndexingAndEvents: 是否使用 RedisIndexedSessionRepository 而不是 RedisSessionRepository。默认值为 false。 * maxInactiveIntervalInSeconds: 会话过期前的时间,以秒为单位。 * redisNamespace: 允许为会话配置特定于应用程序的命名空间。Redis 键和频道 ID 以 <redisNamespace>: 为前缀。 * flushMode: 允许指定何时将数据写入 Redis。默认情况下,仅当在 SessionRepository 上调用 save 时才写入。值为 FlushMode.IMMEDIATE 时,会尽快写入 Redis。

自定义 RedisSerializer

你可以通过创建一个名为 springSessionDefaultRedisSerializer 的 bean 来自定义序列化,该 bean 需要实现 RedisSerializer<Object>

查看 Redis 中的会话

安装 redis-cli 之后,你可以使用 redis-cli 检查 Redis 中的值。例如,你可以在终端窗口中输入以下命令:

$ redis-cli
redis 127.0.0.1:6379> keys *
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" // <1>
bash
  • 该密钥的后缀是 Spring Session 的会话标识符。

您也可以使用 hkeys 命令查看每个会话的属性。以下示例展示了如何操作:

redis 127.0.0.1:6379> hkeys spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021
1) "lastAccessedTime"
2) "creationTime"
3) "maxInactiveInterval"
4) "sessionAttr:username"
redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021 sessionAttr:username
"\xac\xed\x00\x05t\x00\x03rob"
bash

使用 RedisIndexedSessionRepository

RedisIndexedSessionRepository 是一个使用 Spring Data 的 RedisOperations 实现的 SessionRepository。在 Web 环境中,这通常与 SessionRepositoryFilter 结合使用。该实现通过 SessionMessageListener 支持 SessionDestroyedEventSessionCreatedEvent

实例化 RedisIndexedSessionRepository

你可以从下面的示例中看到如何创建一个新实例的典型例子:

RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

// ... configure redisTemplate ...

SessionRepository<? extends Session> repository = new RedisIndexedSessionRepository(redisTemplate);
java

有关如何创建 RedisConnectionFactory 的更多信息,请参阅 Spring Data Redis 参考文档。

使用 @EnableRedisHttpSession(enableIndexingAndEvents = true)

在Web环境中,创建新的 RedisIndexedSessionRepository 的最简单方法是使用 @EnableRedisHttpSession(enableIndexingAndEvents = true)。你可以在 示例和指南(从这里开始) 中找到完整的示例用法。你可以使用以下属性来自定义配置:

  • enableIndexingAndEvents: 是否使用 RedisIndexedSessionRepository 而不是 RedisSessionRepository。默认值为 false

  • maxInactiveIntervalInSeconds: 会话过期前的时间,以秒为单位。

  • redisNamespace: 允许为会话配置特定于应用程序的命名空间。Redis 键和频道 ID 以 <redisNamespace>: 为前缀。

  • flushMode: 允许指定何时将数据写入 Redis。默认情况下,仅在对 SessionRepository 调用 save 时写入。FlushMode.IMMEDIATE 的值会尽快写入 Redis。

自定义 RedisSerializer

你可以通过创建一个名为 springSessionDefaultRedisSerializer 的 bean 来自定义序列化,该 bean 需要实现 RedisSerializer<Object>

Redis TaskExecutor

RedisIndexedSessionRepository 通过使用 RedisMessageListenerContainer 订阅从 Redis 接收事件。你可以通过创建名为 springSessionRedisTaskExecutor 的 bean、名为 springSessionRedisSubscriptionExecutor 的 bean 或同时创建两者来定制这些事件的分发方式。你可以在 这里 找到有关配置 Redis 任务执行器的更多详细信息。

存储详情

以下各节概述了每次操作时 Redis 是如何更新的。下面的示例展示了创建新会话的一个例子:

HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \
maxInactiveInterval 1800 \
lastAccessedTime 1404360000000 \
sessionAttr:attrName someAttrValue \
sessionAttr:attrName2 someAttrValue2
EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
EXPIRE spring:session:expirations1439245080000 2100

后续章节将描述详细信息。

保存会话

每个会话都以 Hash 的形式存储在 Redis 中。每个会话都是通过使用 HMSET 命令进行设置和更新的。以下示例展示了每个会话是如何存储的:

HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \
maxInactiveInterval 1800 \
lastAccessedTime 1404360000000 \
sessionAttr:attrName someAttrValue \
sessionAttr:attrName2 someAttrValue2

在前面的示例中,关于会话的以下陈述是正确的:

  • 会话 ID 是 33fdd1b6-b496-4b33-9f7d-df96679d32fe。

  • 会话创建于 1404360000000(自 1970 年 1 月 1 日零点 GMT 开始的毫秒数)。

  • 会话在 1800 秒(30 分钟)后过期。

  • 会话最后访问时间为 1404360000000(自 1970 年 1 月 1 日零点 GMT 开始的毫秒数)。

  • 会话有两个属性。第一个是 attrName,其值为 someAttrValue。第二个会话属性名为 attrName2,其值为 someAttrValue2

优化的写入

RedisIndexedSessionRepository 管理的 Session 实例会跟踪已更改的属性,并仅更新那些属性。这意味着,如果一个属性写入一次并读取多次,我们只需要写入该属性一次。例如,假设前一节列出的 attrName2 会话属性被更新。保存时将运行以下命令:

HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:attrName2 newValue

会话过期

每个会话都使用 EXPIRE 命令关联一个过期时间,该命令基于 Session.getMaxInactiveInterval()。以下示例显示了一个典型的 EXPIRE 命令:

EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100

请注意,会话实际过期后五分钟后才设置过期时间。这是必要的,以便在会话过期时可以访问会话的值。为了确保会话被清理,会在其实际过期后的五分钟后在会话本身上设置一个过期时间,但前提是在我们执行任何必要的处理之后。

备注

SessionRepository.findById(String) 方法确保不会返回已过期的会话。这意味着在使用会话之前,您无需检查其是否过期。

Spring Session 依赖于 Redis 的删除和过期keyspace notifications来分别触发SessionDeletedEventSessionExpiredEventSessionDeletedEventSessionExpiredEvent确保与Session关联的资源被清理。例如,当你使用 Spring Session 的 WebSocket 支持时,Redis 的过期或删除事件会触发与该会话关联的任何 WebSocket 连接关闭。

会话密钥本身不会直接跟踪过期时间,因为这样会导致会话数据不再可用。相反,使用了一个特殊的会话过期密钥。在前面的示例中,过期密钥如下:

APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800

当会话过期密钥被删除或过期时,keyspace 通知会触发对实际会话的查找,并触发一个 SessionDestroyedEvent

仅依赖 Redis 的过期机制的一个问题是,如果键未被访问,Redis 不保证何时触发过期事件。具体来说,Redis 用于清理过期键的后台任务是一个低优先级任务,可能不会触发键的过期。有关更多详细信息,请参阅 Redis 文档中的 过期事件的时间 部分。

为了解决已过期事件不能保证发生的问题,我们可以通过在键预期过期时访问每个键来确保这一点。这意味着,如果键的 TTL 已经过期,那么当尝试访问该键时,Redis 会移除该键并触发过期事件。

由于这个原因,每个会话的过期时间也以最接近的分钟为单位进行跟踪。这样可以让后台任务访问可能已过期的会话,以确保 Redis 的过期事件以更确定的方式触发。以下示例展示了这些事件:

SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
EXPIRE spring:session:expirations1439245080000 2100

后台任务然后使用这些映射来显式地请求每个键。通过访问键而不是删除它,我们确保只有在TTL过期时Redis才会为我们删除该键。

备注

我们不会显式地删除密钥,因为在某些情况下,可能会出现竞态条件,错误地将未过期的密钥识别为已过期。除了使用分布式锁(这会严重影响我们的性能)之外,没有其他方法可以确保过期映射的一致性。通过简单地访问密钥,我们可以确保只有在该密钥的 TTL 过期时才会被移除。

SessionDeletedEventSessionExpiredEvent

SessionDeletedEventSessionExpiredEvent 都是 SessionDestroyedEvent 的类型。

RedisIndexedSessionRepository 支持在 Session 被删除时触发 SessionDeletedEvent,或在 Session 过期时触发 SessionExpiredEvent。这是为了确保与 Session 相关的资源能够被正确清理。

例如,在与 WebSockets 集成时,SessionDestroyedEvent 负责关闭任何活动的 WebSocket 连接。

触发 SessionDeletedEventSessionExpiredEvent 是通过 SessionMessageListener 完成的,该监听器监听 Redis Keyspace 事件。为了使此功能正常工作,需要启用 Generic 命令和 Expired 事件的 Redis Keyspace 事件。以下示例展示了如何实现这一点:

redis-cli config set notify-keyspace-events Egx
bash

如果你使用 @EnableRedisHttpSession(enableIndexingAndEvents = true),那么管理 SessionMessageListener 和启用必要的 Redis Keyspace 事件会自动完成。然而,在一个安全的 Redis 环境中,config 命令是被禁用的。这意味着 Spring Session 无法为你配置 Redis Keyspace 事件。要禁用自动配置,可以将 ConfigureRedisAction.NO_OP 作为一个 bean 添加。

例如,使用 Java 配置,你可以使用以下内容:

@Bean
ConfigureRedisAction configureRedisAction() {
return ConfigureRedisAction.NO_OP;
}
java

在 XML 配置中,您可以使用以下内容:

<util:constant
static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>
xml

使用 SessionCreatedEvent

当会话创建时,会向 Redis 发送一个事件,频道 ID 为 spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe,其中 33fdd1b6-b496-4b33-9f7d-df96679d32fe 是会话 ID。事件的主体是创建的会话。

如果注册为 MessageListener(默认情况下),RedisIndexedSessionRepository 会将 Redis 消息转换为 SessionCreatedEvent

查看 Redis 中的会话

安装 redis-cli 之后,你可以使用 redis-cli 检查 Redis 中的值。例如,你可以在终端中输入以下命令:

$ redis-cli
redis 127.0.0.1:6379> keys *
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" // <1>
2) "spring:session:expirations:1418772300000" // <2>
bash
  • 该密钥的后缀是 Spring Session 的会话标识符。

  • 此密钥包含在 1418772300000 时应删除的所有会话 ID。

您还可以查看每个会话的属性。以下示例展示了如何操作:

redis 127.0.0.1:6379> hkeys spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021
1) "lastAccessedTime"
2) "creationTime"
3) "maxInactiveInterval"
4) "sessionAttr:username"
redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021 sessionAttr:username
"\xac\xed\x00\x05t\x00\x03rob"
bash

使用 ReactiveRedisSessionRepository

ReactiveRedisSessionRepository 是一个使用 Spring Data 的 ReactiveRedisOperations 实现的 ReactiveSessionRepository。在 Web 环境中,这通常与 WebSessionStore 结合使用。

实例化 ReactiveRedisSessionRepository

以下示例展示了如何创建一个新实例:

// ... create and configure connectionFactory and serializationContext ...

ReactiveRedisTemplate<String, Object> redisTemplate = new ReactiveRedisTemplate<>(connectionFactory,
serializationContext);

ReactiveSessionRepository<? extends Session> repository = new ReactiveRedisSessionRepository(redisTemplate);
java

有关如何创建 ReactiveRedisConnectionFactory 的更多信息,请参阅 Spring Data Redis 参考文档。

使用 @EnableRedisWebSession

在Web环境中,创建新的 ReactiveRedisSessionRepository 的最简单方法是使用 @EnableRedisWebSession。你可以使用以下属性来自定义配置:

  • maxInactiveIntervalInSeconds: 会话过期之前的时间,以秒为单位

  • redisNamespace: 允许为会话配置特定于应用程序的命名空间。Redis 键和频道 ID 以 <redisNamespace>: 前缀开头。

  • flushMode: 允许指定何时将数据写入 Redis。默认情况下,仅在 ReactiveSessionRepository 上调用 save 时写入。值为 FlushMode.IMMEDIATE 时,会尽快写入 Redis。

优化的写操作

ReactiveRedisSessionRepository 管理的 Session 实例会跟踪已更改的属性,并仅更新那些属性。这意味着,如果一个属性被写入一次并读取多次,我们只需要写入该属性一次即可。

查看 Redis 中的会话

安装 redis-cli 之后,你可以使用 redis-cli 检查 Redis 中的值。例如,你可以在终端窗口中输入以下命令:

$ redis-cli
redis 127.0.0.1:6379> keys *
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" // <1>
bash
  • 该密钥的后缀是 Spring Session 的会话标识符。

您也可以使用 hkeys 命令来查看每个会话的属性。以下示例展示了如何操作:

redis 127.0.0.1:6379> hkeys spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021
1) "lastAccessedTime"
2) "creationTime"
3) "maxInactiveInterval"
4) "sessionAttr:username"
redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021 sessionAttr:username
"\xac\xed\x00\x05t\x00\x03rob"
bash

使用 MapSessionRepository

MapSessionRepository 允许将 Session 持久化到 Map 中,键是 Session ID,值是 Session。你可以使用 ConcurrentHashMap 的实现作为测试或便捷机制。或者,你也可以将其与分布式 Map 实现一起使用。例如,它可以与 Hazelcast 一起使用。

实例化 MapSessionRepository

以下示例展示了如何创建一个新实例:

SessionRepository<? extends Session> repository = new MapSessionRepository(new ConcurrentHashMap<>());
java

使用 Spring Session 和 Hazlecast

Hazelcast 示例是一个完整的应用程序,它演示了如何使用 Hazelcast 的 Spring Session。

要运行它,请使用以下命令:

./gradlew :samples:hazelcast:tomcatRun

Hazelcast Spring 示例 是一个完整的应用程序,演示了如何将 Spring Session 与 Hazelcast 和 Spring Security 一起使用。

它包括支持触发 SessionCreatedEventSessionDeletedEventSessionExpiredEvent 的示例 Hazelcast MapListener 实现。

要运行它,请使用以下命令:

./gradlew :samples:hazelcast-spring:tomcatRun

使用 ReactiveMapSessionRepository

ReactiveMapSessionRepository 允许将 Session 持久化到一个 Map 中,键是 Session ID,值是 Session。你可以使用 ConcurrentHashMap 的实现作为测试或便捷机制。或者,你也可以将其与分布式 Map 实现一起使用,但要求提供的 Map 必须是非阻塞的。

使用 JdbcIndexedSessionRepository

JdbcIndexedSessionRepository 是一个 SessionRepository 实现,它使用 Spring 的 JdbcOperations 将会话存储在关系型数据库中。在网络环境中,这通常与 SessionRepositoryFilter 结合使用。请注意,此实现不支持会话事件的发布。

实例化 JdbcIndexedSessionRepository

以下示例展示了如何创建一个新实例:

JdbcTemplate jdbcTemplate = new JdbcTemplate();

// ... configure jdbcTemplate ...

TransactionTemplate transactionTemplate = new TransactionTemplate();

// ... configure transactionTemplate ...

SessionRepository<? extends Session> repository = new JdbcIndexedSessionRepository(jdbcTemplate,
transactionTemplate);
java

有关如何创建和配置 JdbcTemplatePlatformTransactionManager 的更多信息,请参阅 Spring 框架参考文档

使用 @EnableJdbcHttpSession

在 Web 环境中,创建新的 JdbcIndexedSessionRepository 的最简单方法是使用 @EnableJdbcHttpSession。你可以在 示例和指南(从这里开始) 中找到完整的示例用法。你可以使用以下属性来自定义配置:

  • tableName: 用于存储会话的数据库表的名称

  • maxInactiveIntervalInSeconds: 会话过期前的时间(以秒为单位)

自定义 LobHandler

你可以通过创建一个名为 springSessionLobHandler 的 bean 来自定义 BLOB 处理,该 bean 需要实现 LobHandler 接口。

自定义 ConversionService

你可以通过提供一个 ConversionService 实例来自定义会话的默认序列化和反序列化。在典型的 Spring 环境中,默认的 ConversionService bean(名为 conversionService)会被自动选取并用于序列化和反序列化。但是,你可以通过提供一个名为 springSessionConversionService 的 bean 来覆盖默认的 ConversionService

存储详情

默认情况下,此实现使用 SPRING_SESSIONSPRING_SESSION_ATTRIBUTES 表来存储会话。请注意,您可以自定义表名,如前所述。在这种情况下,用于存储属性的表的名称是通过提供的表名加上 _ATTRIBUTES 后缀来命名的。如果需要进一步的自定义,您可以使用 set*Query 设置方法来自定义仓库使用的 SQL 查询。在这种情况下,您需要手动配置 sessionRepository bean。

由于不同数据库供应商之间存在差异,特别是在存储二进制数据时,请确保使用特定于您的数据库的 SQL 脚本。大多数主要数据库供应商的脚本被打包为 org/springframework/session/jdbc/schema-*.sql,其中 * 是目标数据库类型。

例如,对于 PostgreSQL,您可以使用以下模式脚本:

CREATE TABLE SPRING_SESSION (
PRIMARY_ID CHAR(36) NOT NULL,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
EXPIRY_TIME BIGINT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
);

CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BYTEA NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
);
sql

使用 MySQL 数据库时,您可以使用以下脚本:

CREATE TABLE SPRING_SESSION (
PRIMARY_ID CHAR(36) NOT NULL,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
EXPIRY_TIME BIGINT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;

CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
sql

事务管理

JdbcIndexedSessionRepository 中的所有 JDBC 操作都是以事务方式执行的。为了防止由于与现有事务的干扰而导致意外行为(例如,在已经参与只读事务的线程中运行 save 操作),事务是以 REQUIRES_NEW 传播级别执行的。

使用 HazelcastIndexedSessionRepository

HazelcastIndexedSessionRepository 是一个 SessionRepository 实现,它将会话存储在 Hazelcast 的分布式 IMap 中。在网络环境中,这通常与 SessionRepositoryFilter 结合使用。

实例化 HazelcastIndexedSessionRepository

以下示例展示了如何创建一个新实例:

Config config = new Config();

// ... configure Hazelcast ...

HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);

HazelcastIndexedSessionRepository repository = new HazelcastIndexedSessionRepository(hazelcastInstance);
java

有关如何创建和配置 Hazelcast 实例的更多信息,请参阅 Hazelcast 文档

使用 @EnableHazelcastHttpSession

要使用 Hazelcast 作为 SessionRepository 的支持源,可以将 @EnableHazelcastHttpSession 注解添加到 @Configuration 类中。这样做扩展了 @EnableSpringHttpSession 注解提供的功能,但会在 Hazelcast 中为你创建 SessionRepository。你必须提供一个单独的 HazelcastInstance bean 才能使配置生效。你可以在 Samples and Guides (Start Here) 中找到完整的配置示例。

基本自定义

你可以在 @EnableHazelcastHttpSession 上使用以下属性来自定义配置:

  • maxInactiveIntervalInSeconds: 会话到期前的时间,以秒为单位。默认值为 1800 秒(30 分钟)

  • sessionMapName: 用于在 Hazelcast 中存储会话数据的分布式 Map 的名称。

会话事件

使用 MapListener 响应分布式 Map 中条目的添加、淘汰和删除,会导致这些事件分别触发通过 ApplicationEventPublisher 发布 SessionCreatedEventSessionExpiredEventSessionDeletedEvent 事件。

存储详情

会话存储在 Hazelcast 中的分布式 IMap 中。IMap 接口方法用于 get()put() 会话。此外,values() 方法支持 FindByIndexNameSessionRepository#findByIndexNameAndIndexValue 操作,以及适当的 ValueExtractor(需要在 Hazelcast 中注册)。有关此配置的更多详细信息,请参阅 Hazelcast Spring 示例IMap 中会话的过期由 Hazelcast 对在将条目 put()IMap 时设置生存时间的支持来处理。空闲时间超过生存时间的条目(会话)将自动从 IMap 中移除。

您不需要在Hazelcast配置中为IMap设置诸如max-idle-secondstime-to-live-seconds之类的参数。

请注意,如果你使用 Hazelcast 的 MapStore 来持久化你的会话 IMap,在从 MapStore 重新加载会话时,以下限制适用:

  • 重新加载触发 EntryAddedListener 会导致 SessionCreatedEvent 被重新发布

  • 重新加载使用给定 IMap 的默认 TTL 会导致会话失去其原始 TTL

使用 CookieSerializer

CookieSerializer 负责定义会话 cookie 的写入方式。Spring Session 提供了一个使用 DefaultCookieSerializer 的默认实现。

CookieSerializer 暴露为 bean

CookieSerializer 暴露为 Spring bean 会在你使用 @EnableRedisHttpSession 等配置时增强现有的配置。

以下示例展示了如何这样做:

@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID"); 1
serializer.setCookiePath("/"); 2
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); 3
return serializer;
}
java
  • 我们将 cookie 的名称自定义为 JSESSIONID

  • 我们将 cookie 的路径自定义为 /(而不是默认的上下文根)。

  • 我们将域名模式(一个正则表达式)自定义为 ^.?\\.(\\w\\.[a-z]+)$。这允许在不同域和应用程序之间共享会话。如果正则表达式不匹配,则不会设置任何域,并使用现有域。如果正则表达式匹配,则使用第一个分组作为域。这意味着对 child.example.com 的请求会将域设置为 example.com。然而,对 localhost:8080/192.168.1.100:8080/ 的请求不会设置 cookie,因此,在开发环境中仍然可以正常工作,而无需在生产环境中进行任何更改。

注意

您应该仅匹配有效的域名字符,因为域名会反映在响应中。这样做可以防止恶意用户执行诸如HTTP 响应拆分之类的攻击。

自定义 CookieSerializer

你可以通过在 DefaultCookieSerializer 上使用以下任何配置选项来自定义会话 cookie 的写入方式。

  • cookieName: 要使用的 cookie 的名称。默认值:SESSION

  • useSecureCookie: 指定是否应使用安全的 cookie。默认值:使用创建时 HttpServletRequest.isSecure() 的值。

  • cookiePath: cookie 的路径。默认值:上下文根。

  • cookieMaxAge: 指定在会话创建时设置的 cookie 的最大生存期。默认值:-1,表示浏览器关闭时应删除 cookie。

  • jvmRoute: 指定要附加到会话 ID 并包含在 cookie 中的后缀。用于标识会话亲和性要路由到的 JVM。对于某些实现(例如 Redis),此选项不会提供性能优势。但是,它可以帮助跟踪特定用户的日志。

  • domainName: 允许指定用于 cookie 的特定域名。此选项易于理解,但通常需要在开发环境和生产环境之间进行不同的配置。请参阅 domainNamePattern 作为替代方案。

  • domainNamePattern: 用于从 HttpServletRequest#getServerName() 提取域名的不区分大小写的模式。该模式应提供一个用于提取 cookie 域值的单一分组。如果正则表达式不匹配,则不设置域并使用现有域。如果正则表达式匹配,则使用第一个分组作为域。

  • sameSite: SameSite cookie 指令的值。要禁用 SameSite cookie 指令的序列化,可以将此值设置为 null。默认值:Lax

注意

您应该仅匹配有效的域名字符,因为域名会被反映在响应中。这样做可以防止恶意用户执行诸如HTTP 响应拆分之类的攻击。

自定义 SessionRepository

实现自定义 SessionRepository API 应该是一个相当直接的任务。将自定义实现与 @EnableSpringHttpSession 支持结合使用,可以让您重用现有的 Spring Session 配置设施和基础设施。然而,有几个方面需要更仔细地考虑。

在 HTTP 请求的生命周期中,HttpSession 通常会被持久化到 SessionRepository 两次。第一次持久化操作是为了确保客户端一旦获得会话 ID 就可以访问该会话,并且在会话提交后也需要写入,因为之后可能会对会话进行进一步的修改。考虑到这一点,我们通常建议 SessionRepository 实现跟踪更改以确保仅保存增量。这在高度并发的环境中尤为重要,在这种环境中,多个请求会对同一个 HttpSession 进行操作,因此会导致竞争条件,请求会相互覆盖对会话属性的更改。Spring Session 提供的所有 SessionRepository 实现都使用了上述方法来持久化会话更改,并且在实现自定义 SessionRepository 时可以用作参考。

请注意,实现自定义 ReactiveSessionRepository 时也适用同样的建议。在这种情况下,您应该使用 @EnableSpringWebSession