跳到主要内容
版本:4.1.1

API 文档

Doubao 中英对照 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 ...

}
  • 我们创建一个带有泛型类型 S 的 SessionRepository 实例,S 扩展自 Session。该泛型类型在我们的类中定义。

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

  • 我们与 Session 交互。在示例中,我们演示将一个 User 保存到 Session。

  • 我们现在保存 Session。这就是我们需要泛型类型 S 的原因。SessionRepository 只允许保存由同一个 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 ...

}
  • 我们创建一个带有泛型类型 S 的 SessionRepository 实例,S 扩展自 Session。该泛型类型在我们的类中定义。

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

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

  • 我们现在保存 Session。这就是我们需要泛型类型 S 的原因。SessionRepository 只允许保存由同一个 SessionRepository 创建或检索的 Session 实例。这使得 SessionRepository 可以进行特定于实现的优化(即只写入已更改的属性)。保存 Session 时会自动更新最后访问时间。

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

使用 SessionRepository​

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

如果可能,你不应直接与 SessionRepository 或 Session 交互。相反,开发人员应通过 HttpSession 和 WebSocket 集成间接与 SessionRepository 和 Session 交互。

使用 FindByIndexNameSessionRepository​

Spring Session 用于操作 Session 的最基本 API 是 SessionRepository。该 API 特意设计得非常简单,以便你可以轻松提供具有基本功能的额外实现。

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

FindByIndexNameSessionRepository 提供了一个方法,用于查找具有给定索引名称和索引值的所有会话。作为所有提供的 FindByIndexNameSessionRepository 实现都支持的常见用例,你可以使用一个便捷方法来查找特定用户的所有会话。这是通过确保名称为 FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME 的会话属性填充了用户名来完成的。确保填充该属性是你的责任,因为 Spring Session 不知道正在使用的认证机制。以下清单展示了如何使用它的示例:

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

FindByIndexNameSessionRepository 的某些实现提供了钩子,可自动为其他会话属性建立索引。例如,许多实现会自动确保当前 Spring Security 用户名以 FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME 作为索引名称进行索引。

会话建立索引后,你可以使用类似以下的代码进行查找:

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

使用 ReactiveSessionRepository​

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

如果可能,你不应直接与 ReactiveSessionRepository 或 Session 交互。相反,你应通过 WebSession 集成间接与 ReactiveSessionRepository 和 Session 交互。

使用 @EnableSpringHttpSession​

你可以将 @EnableSpringHttpSession 注解添加到 @Configuration 类上,以将 SessionRepositoryFilter 暴露为名为 springSessionRepositoryFilter 的 bean。为使用该注解,你必须提供单个 SessionRepository bean。以下示例展示了如何这样做:

@EnableSpringHttpSession
@Configuration
public class SpringHttpSessionConfig {

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

}

请注意,没有为你配置会话过期的基础架构。这是因为会话过期等事项高度依赖于具体实现。这意味着,如果你需要清理过期会话,则由你负责清理过期会话。

使用 @EnableSpringWebSession​

你可以将 @EnableSpringWebSession 注解添加到 @Configuration 类上,以将 WebSessionManager 暴露为名为 webSessionManager 的 bean。要使用该注解,你必须提供单个 ReactiveSessionRepository bean。以下示例展示了如何这样做:

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

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

}

请注意,没有为你配置会话过期的基础架构。这是因为会话过期等事项高度依赖于具体实现。这意味着,如果你需要清理过期会话,则由你负责清理过期会话。

使用 RedisSessionRepository​

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

实例化 RedisSessionRepository​

你可以在以下清单中看到如何创建新实例的典型示例:

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

// ... configure redisTemplate ...

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

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

使用 @EnableRedisHttpSession​

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

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

自定义 RedisSerializer​

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

在 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>
  • 此键的后缀是 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"

使用 RedisIndexedSessionRepository​

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

实例化 RedisIndexedSessionRepository​

你可以在以下清单中看到如何创建新实例的典型示例:

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

// ... configure redisTemplate ...

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

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

使用 @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、实现 RedisSerializer<Object> 的 bean 来自定义序列化。

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

后续各节描述了细节。

保存会话​

每个会话在 Redis 中存储为一个 Hash。每个会话使用 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 的删除和过期 键空间通知,分别触发 SessionDeletedEvent 和 SessionExpiredEvent。SessionDeletedEvent 或 SessionExpiredEvent 确保与 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

当会话过期键被删除或过期时,键空间通知会触发对实际会话的查找,并触发 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 已过期时才会移除该键。

SessionDeletedEvent 与 SessionExpiredEvent​

SessionDeletedEvent 和 SessionExpiredEvent 都是 SessionDestroyedEvent 的类型。

当 Session 被删除时触发 SessionDeletedEvent,或当 Session 过期时触发 SessionExpiredEvent,RedisIndexedSessionRepository 对此提供支持。这是必要的,以确保与 Session 关联的资源被正确清理。

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

触发 SessionDeletedEvent 或 SessionExpiredEvent 是通过 SessionMessageListener 提供的,它监听 Redis 键空间事件。为使其工作,需要启用 Generic 命令和过期事件的 Redis 键空间事件。以下示例展示了如何这样做:

redis-cli config set notify-keyspace-events Egx

如果你使用 @EnableRedisHttpSession(enableIndexingAndEvents = true),则管理 SessionMessageListener 和启用必要的 Redis 键空间事件会自动完成。但是,在受保护的 Redis 环境中,config 命令被禁用。这意味着 Spring Session 无法为你配置 Redis 键空间事件。要禁用自动配置,添加 ConfigureRedisAction.NO_OP 作为 bean。

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

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

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

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

使用 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>
  • 此键的后缀是 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"

使用 ReactiveRedisSessionRepository​

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

实例化 ReactiveRedisSessionRepository​

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

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

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

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

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

使用 @EnableRedisWebSession​

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

  • maxInactiveIntervalInSeconds:会话过期前的时间量,以秒为单位
  • redisNamespace:允许为会话配置应用程序特定的命名空间。Redis 键和频道 ID 以 <redisNamespace>: 的 q 前缀开头。
  • 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>
  • 此键的后缀是 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"

使用 MapSessionRepository​

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

实例化 MapSessionRepository​

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

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

使用 ReactiveMapSessionRepository​

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

使用 JdbcIndexedSessionRepository​

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

实例化 JdbcIndexedSessionRepository​

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

JdbcTemplate jdbcTemplate = new JdbcTemplate();

// ... configure jdbcTemplate ...

TransactionTemplate transactionTemplate = new TransactionTemplate();

// ... configure transactionTemplate ...

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

有关如何创建和配置 JdbcTemplate 与 PlatformTransactionManager 的更多信息,请参阅 Spring Framework Reference Documentation。

使用 @EnableJdbcHttpSession​

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

  • tableName:Spring Session 用于存储会话的数据库表名称
  • maxInactiveIntervalInSeconds:会话过期前的时间量,以秒为单位

自定义 LobHandler​

你可以通过创建一个名为 springSessionLobHandler、实现 LobHandler 的 bean 来自定义 BLOB 处理。

自定义 ConversionService​

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

存储细节​

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

由于各种数据库厂商之间的差异,尤其是在存储二进制数据方面,请确保使用特定于你的数据库的 SQL 脚本。大多数主流数据库厂商的脚本打包为 org/springframework/session/jdbc/schema-*.sql,其中 * 是目标数据库类型。

例如,对于 PostgreSQL,你可以使用以下 schema 脚本:

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
);

对于 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;

事务管理​

JdbcIndexedSessionRepository 中的所有 JDBC 操作都以事务方式执行。事务的传播行为设置为 REQUIRES_NEW,以避免因与现有事务相互干扰而导致意外行为(例如,在一个已参与只读事务的线程中运行 save 操作)。

会话事件​

使用 MapListener 响应分布式 Map 中条目的添加、逐出和移除,会导致这些事件分别通过 ApplicationEventPublisher 发布 SessionCreatedEvent、SessionExpiredEvent 和 SessionDeletedEvent 事件。

使用 CookieSerializer​

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

将 CookieSerializer 暴露为 bean​

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

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

@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID"); 1
serializer.setCookiePath("/"); 2
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); 3
return serializer;
}
  • 我们将 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。