Spring Session

Spring Session 提供了用于 Management 用户会话信息的 API 和实现。

Introduction

Spring Session 提供了用于 Management 用户会话信息的 API 和实现。它还提供了透明的集成:

  • HttpSession-允许以应用程序容器(即 Tomcat)中立的方式替换 HttpSession。其他功能包括:

  • 集群会话 -Spring Session 使得支持clustered sessions变得很简单,而不必依赖于特定于应用程序容器的解决方案。

    • 多个浏览器会话 -Spring Session 在单个浏览器实例(即类似于 Google 的多个经过身份验证的帐户)中支持Management 多个用户的会话

    • RESTful APIs -Spring Session 允许在 Headers 中提供会话 ID 以与RESTful APIs一起使用

  • WebSocket-能够在接收 WebSocket 消息时使HttpSession保持活动状态

1.3 的新功能

以下是 Spring Session 1.3 新增功能的重点。您可以通过参考1.3.0.M11.3.0.M21.3.0.RC11.3.0.RELEASE的更改日志找到新功能的完整列表。

示例和指南(从这里开始)

如果您希望开始使用 Spring Session,最好的起点是我们的示例应用程序。

表 1.示例应用程序

SourceDescriptionGuide
HttpSession演示如何使用 Spring Session 用 Redis 存储替换HttpSessionHttpSession Guide
HttpSession XML演示如何使用 Spring Session 通过基于 XML 的配置用 Redis 存储替换HttpSessionHttpSession XML 指南
使用 Spring Boot 与 GemFire 进行 HttpSession演示如何在 Spring Boot 应用程序中使用 Client 端/服务器拓扑使用 Spring Session 将HttpSession替换为 GemFire。HttpSession GemFire 启动指南
带有 GemFire 的 HttpSession(Client 端/服务器)演示如何使用 Spring Session 通过 Client 端/服务器拓扑结构将HttpSession替换为 GemFire。HttpSession GemFireClient 端/服务器指南
使用 XML 的 HttpSession 与 GemFire(Client 端/服务器)演示如何使用 Spring Session 通过配置有 XML 的 Client 端/服务器拓扑,使用 GemFire 将HttpSession替换为 GemFire。HttpSession GemFireClient 端/服务器 XML 指南
具有 GemFire(P2P)的 HttpSession演示如何使用 Spring Session 通过 P2P 拓扑将 G_Fire 替换为HttpSessionHttpSession GemFire P2P 指南
使用 XML 的 HttpSession 与 GemFire(P2P)演示如何使用 Spring Session 通过配置有 XML 的 P2P 拓扑将 G_Fire 替换为HttpSessionHttpSession GemFire P2P XML 指南
Custom Cookie演示如何使用 Spring Session 和自定义 cookie。自定义 Cookie 指南
Spring Boot演示如何在 Spring Boot 中使用 Spring Session。Spring 启动指南
Grails 3演示如何在 Grails 3 中使用 Spring Session。Grails 3 指南
Spring Security演示如何将 Spring Session 与现有的 Spring Security 应用程序一起使用。Spring Security 指南
REST演示如何在 REST 应用程序中使用 Spring Session 支持通过 Headers 进行身份验证。REST Guide
按用户名查找演示如何使用 Spring Session 通过用户名查找会话。按用户名查找
Multiple Users演示如何使用 Spring SessionManagement 多个同时的浏览器会话(即 Google 帐户)。Management 多个用户指南
WebSocket演示如何将 Spring Session 与 WebSockets 结合使用。WebSocket Guide
Mongo演示如何在 Mongo 中使用 Spring Session。Mongo Guide

Hazelcast演示如何将 Spring Session 与 Hazelcast 结合使用。TBD
Hazelcast Spring演示如何在现有的 Spring Security 应用程序中使用 Spring Session 和 Hazelcast。 HazelcastSpring 指南
HttpSession JDBC演示如何使用 Spring Session 用关系数据库存储替换HttpSessionHttpSession JDBC 指南
HttpSession JDBC XML演示如何使用 Spring Session 使用基于 XML 的配置将HttpSession替换为关系数据库存储。 HttpSession JDBC XML 指南
HttpSession JDBC Spring Boot演示了在使用 Spring Boot 时如何使用 Spring Session 用关系数据库存储替换HttpSessionHttpSession JDBC Spring 引导指南

HttpSession Integration

Spring Session 提供与HttpSession的透明集成。这意味着开发人员可以使用 Spring Session 支持的实现切换HttpSession实现。

为什么选择 Spring Session 和 HttpSession?

我们已经提到过,Spring Session 提供了与HttpSession的透明集成,但是我们可以从中获得什么好处呢?

  • 集群会话 -Spring Session 使得支持clustered sessions变得很简单,而不必依赖于特定于应用程序容器的解决方案。

  • 多个浏览器会话 -Spring Session 在单个浏览器实例(即类似于 Google 的多个经过身份验证的帐户)中支持Management 多个用户的会话

  • RESTful APIs -Spring Session 允许在 Headers 中提供会话 ID 以与RESTful APIs一起使用

带有 Redis 的 HttpSession

通过在使用HttpSession的任何内容之前添加一个 Servlet 过滤器来启用将 Spring Session 与HttpSession一起使用。您可以使用以下任一方法来启用此功能:

Redis 基于 Java 的配置

本节介绍如何使用 Redis 通过基于 Java 的配置来备份HttpSession

Note

HttpSession Sample提供了有关如何使用 Java 配置集成 Spring Session 和HttpSession的工作示例。您可以阅读下面的集成基本步骤,但是建议您在与自己的应用程序集成时遵循详细的 HttpSession 指南。

Spring Java 配置

添加所需的依赖关系之后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器,该过滤器将HttpSession实现替换为 Spring Session 支持的实现。添加以下 Spring 配置:

@EnableRedisHttpSession (1)
public class Config {

        @Bean
        public LettuceConnectionFactory connectionFactory() {
                return new LettuceConnectionFactory(); (2)
        }
}
  • (1) @EnableRedisHttpSessionComments 创建一个名称为springSessionRepositoryFilter的 Spring Bean,用于实现 Filter。过滤器负责替换由 Spring Session 支持的HttpSession实现。在这种情况下,Spring Session 由 Redis 支持。
  • (2) 我们创建一个RedisConnectionFactory,将 Spring Session 连接到 Redis Server。我们将连接配置为在默认端口(6379)上连接到 localhost。有关配置 Spring Data Redis 的更多信息,请参阅reference documentation
Java Servlet 容器初始化

我们的Spring Configuration创建了一个实现Filter的名为springSessionRepositoryFilter的 Spring Bean。 springSessionRepositoryFilter bean 负责用 Spring Session 支持的自定义实现替换HttpSession

为了让我们的Filter发挥其魔力,Spring 需要加载我们的Config类。最后,我们需要确保我们的 Servlet 容器(即 Tomcat)对每个请求都使用springSessionRepositoryFilter。幸运的是,Spring Session 提供了一个名为AbstractHttpSessionApplicationInitializer的 Util 类,这两个步骤都非常容易。您可以在下面找到一个示例:

src/main/java/sample/Initializer.java

public class Initializer extends AbstractHttpSessionApplicationInitializer { (1)

        public Initializer() {
                super(Config.class); (2)
        }
}

Note

我们的类(Initializer)的名称无关紧要。重要的是我们扩展AbstractHttpSessionApplicationInitializer

  • (1) 第一步是扩展AbstractHttpSessionApplicationInitializer。这可以确保为每个请求向我们的 Servlet 容器注册名称为springSessionRepositoryFilter的 Spring Bean。
  • (2) AbstractHttpSessionApplicationInitializer还提供了一种机制,可以轻松确保 Spring 加载我们的Config

基于 Redis XML 的配置

本节介绍如何使用 Redis 通过基于 XML 的配置来备份HttpSession

Note

HttpSession XML 示例提供了有关如何使用 XML 配置集成 Spring Session 和HttpSession的工作示例。您可以阅读下面的集成基本步骤,但是建议您在与自己的应用程序集成时遵循详细的 HttpSession XML 指南。

Spring XML 配置

添加所需的依赖关系之后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器,该过滤器将HttpSession实现替换为 Spring Session 支持的实现。添加以下 Spring 配置:

src/main/webapp/WEB-INF/spring/session.xml

(1)
<context:annotation-config/>
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>

(2)
<bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
  • (1) 我们使用<context:annotation-config/>RedisHttpSessionConfiguration的组合,因为 Spring Session 尚未提供 XML 命名空间支持(请参阅gh-104)。这将创建一个名称为springSessionRepositoryFilter的 Spring Bean,它实现了 Filter。过滤器负责替换由 Spring Session 支持的HttpSession实现。在这种情况下,Spring Session 由 Redis 支持。
  • (2) 我们创建一个RedisConnectionFactory,将 Spring Session 连接到 Redis Server。我们将连接配置为在默认端口(6379)上连接到 localhost。有关配置 Spring Data Redis 的更多信息,请参阅reference documentation
XML Servlet 容器初始化

我们的Spring Configuration创建了一个实现Filter的名为springSessionRepositoryFilter的 Spring Bean。 springSessionRepositoryFilter bean 负责用 Spring Session 支持的自定义实现替换HttpSession

为了使Filter发挥其魔力,我们需要指示 Spring 加载session.xml配置。我们使用以下配置进行此操作:

src/main/webapp/WEB-INF/web.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/spring/*.xml
    </param-value>
</context-param>
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

ContextLoaderListener读取 contextConfigLocation 并选择我们的 session.xml 配置。

最后,我们需要确保我们的 Servlet 容器(即 Tomcat)对每个请求都使用springSessionRepositoryFilter。以下代码段为我们执行了最后一步:

src/main/webapp/WEB-INF/web.xml

<filter>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

DelegatingFilterProxy将查找名称为springSessionRepositoryFilter的 Bean 并将其转换为Filter。对于每个调用DelegatingFilterProxy的请求,都将调用springSessionRepositoryFilter

具有 Pivotal GemFire 的 HttpSession

Pivotal GemFire与 Spring Session 一起使用时,可以将 Web 应用程序的HttpSession替换为由 GemFireManagement 的“集群”实现,并可以通过 Spring Session 的 API 方便地进行访问。

使用 GemFireManagementSpring Session 的两种最常见的拓扑包括:

此外,GemFire 支持使用WAN functionality进行站点到站点的复制。配置和使用 GemFire 的 WAN 支持的能力与 Spring Session 无关,并且超出了本文档的范围。有关 GemFire WAN 功能的更多详细信息,请参见here

GemFire Client-Server

在 Spring Session 中使用 GemFire 作为提供者时,Client-Server拓扑可能是用户更常见的配置首选项,因为与应用程序相比,GemFire 服务器将具有明显不同且独特的 JVM 堆要求。使用 Client 端-服务器拓扑使应用程序能够独立于其他应用程序进程来 Management(例如复制)应用程序状态。

在 Client 端-服务器拓扑中,使用 Spring Session 的应用程序将打开与(远程)GemFire 服务器群集的 Client 端缓存连接,以 Management 并提供对所有HttpSession状态的一致访问。

您可以使用以下任一配置 Client 端-服务器拓扑:

GemFireClient 端-服务器基于 Java 的配置

本节介绍如何使用 GemFire 的 Client 端-服务器拓扑结构来支持基于 Java 的HttpSession配置。

Note

带有 GemFire 的 HttpSession(Client 端-服务器)示例提供了有关如何使用 Java 配置集成 Spring Session 和 GemFire 以替换 HttpSession 的工作示例。您可以阅读下面的集成基本步骤,但是在与自己的应用程序集成时,建议您遵循详细的 HttpSession with GemFire(Client-Server)指南。

Spring Java 配置

添加所需的依赖项和存储库声明后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器,以 Spring Session 和 GemFire 支持的实现替换HttpSession

添加以下 Spring 配置:

@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 30, poolName = "DEFAULT") (1)
public class ClientConfig {

        static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(20);

        static final CountDownLatch latch = new CountDownLatch(1);

        static final String DEFAULT_GEMFIRE_LOG_LEVEL = "warning";

        @Bean
        static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
                return new PropertySourcesPlaceholderConfigurer();
        }

        Properties gemfireProperties() { (2)
                Properties gemfireProperties = new Properties();
                gemfireProperties.setProperty("name", applicationName());
                gemfireProperties.setProperty("log-level", logLevel());
                return gemfireProperties;
        }

        String applicationName() {
                return "samples:httpsession-gemfire-clientserver:"
                        .concat(getClass().getSimpleName());
        }

        String logLevel() {
                return System.getProperty("sample.httpsession.gemfire.log-level",
                        DEFAULT_GEMFIRE_LOG_LEVEL);
        }

        @Bean
        ClientCacheFactoryBean gemfireCache(
                        @Value("${spring.session.data.gemfire.port:" + ServerConfig.SERVER_PORT + "}") int port) { (3)

                ClientCacheFactoryBean clientCacheFactory = new ClientCacheFactoryBean();

                clientCacheFactory.setClose(true);
                clientCacheFactory.setProperties(gemfireProperties());

                // GemFire Pool settings (4)
                clientCacheFactory.setKeepAlive(false);
                clientCacheFactory.setPingInterval(TimeUnit.SECONDS.toMillis(5));
                clientCacheFactory.setReadTimeout(2000); // 2 seconds
                clientCacheFactory.setRetryAttempts(1);
                clientCacheFactory.setSubscriptionEnabled(true);
                clientCacheFactory.setThreadLocalConnections(false);

                clientCacheFactory.setServers(Collections.singletonList(
                        newConnectionEndpoint(ServerConfig.SERVER_HOST, port)));

                return clientCacheFactory;
        }

        ConnectionEndpoint newConnectionEndpoint(String host, int port) {
                return new ConnectionEndpoint(host, port);
        }

        @Bean
        BeanPostProcessor gemfireCacheServerReadyBeanPostProcessor() { (5)
                return new BeanPostProcessor() {

                        public Object postProcessBeforeInitialization(Object bean, String beanName)
                                throws BeansException {

                                if ("gemfirePool".equals(beanName)) {
                                        ClientMembership.registerClientMembershipListener(
                                                new ClientMembershipListenerAdapter() {
                                                        @Override
                                                        public void memberJoined(ClientMembershipEvent event) {
                                                                latch.countDown();
                                                        }
                                                });
                                }

                                return bean;
                        }

                        public Object postProcessAfterInitialization(Object bean, String beanName)
                                throws BeansException {

                                if (bean instanceof Pool && "gemfirePool".equals(beanName)) {
                                        try {
                                                Assert.state(latch.await(DEFAULT_WAIT_DURATION, TimeUnit.MILLISECONDS),
                                                        String.format("GemFire Cache Server failed to start on host [%1$s] and port [%2$d]",
                                                                ServerConfig.SERVER_HOST, ServerConfig.SERVER_PORT));
                                        }
                                        catch (InterruptedException e) {
                                                Thread.currentThread().interrupt();
                                        }
                                }

                                return bean;
                        }
                };
        }
  • (1) @EnableGemFireHttpSessionComments 创建一个实现Filter的名为springSessionRepositoryFilter的 Spring bean。过滤器是由 Spring Session 和 GemFire 支持的实现替换HttpSession的。
  • (2) 接下来,我们注册一个Properties bean,该 bean 使我们能够使用GemFire 的系统属性配置 GemFireClient 端缓存的某些方面。
  • (3) 我们使用Properties来配置 GemFire ClientCache的实例。
  • (4) 然后,我们在 Client 端/服务器拓扑中配置PoolClient 端连接以与 GemFire 服务器对话。在我们的配置中,我们对超时,连接数等使用了合理的设置。同样,已将Pool配置为直接连接到服务器。从PoolFactory API了解有关各种Pool配置设置的更多信息。
  • (5) 最后,我们包含一个 Spring BeanPostProcessor来阻止 Client 端,直到我们的 GemFire 服务器启动并运行,侦听并接受 Client 端连接为止。

gemfireCacheServerReadyBeanPostProcessor是必需的,以便在测试过程中以自动化的方式协调 Client 端和服务器,但是在 GemFire 集群目前已经运行的情况下(例如在 Producing)则不需要。

BeanPostProcessor使用 GemFire ClientMembershipListener,当 Client 端成功连接到服务器时,该通知会通知。构建连接后,侦听器将释放postProcessAfterInitialization回调中BeanPostProcessorawait 的闩锁(直到指定的超时)以阻止 Client 端。

Tip

在群集可能包含数百个 GemFire 数据节点(服务器)的典型 GemFire 部署中,Client 端连接到群集中运行的一个或多个 GemFire Locator 更为常见。定位器将有关可用服务器,负载以及哪些服务器具有感兴趣的 Client 端数据的元数据传递给 Client 端,这对于单跳直接数据访问特别重要。查看有关GemFire 用户指南中的 Client 端/服务器拓扑的更多详细信息。

Note

有关配置* Spring Data GemFire *的更多信息,请参阅reference guide

@EnableGemFireHttpSessionComments 使开发人员可以使用以下属性立即配置 Spring Session 和 GemFire 的某些方面:

  • maxInactiveIntervalInSeconds-控制* HttpSession *空闲超时时间(默认为 30 分钟 )。

  • regionName-指定用于存储HttpSession状态的 GemFire 区域的名称(默认值为“ ClusteredSpringSessions ”)。

  • clientRegionShort-用 GemFire ClientRegionShortcut指定 GemFire 的数据 ManagementPolicy(默认值为PROXY)。此属性仅在配置 Client 端区域时使用。

  • poolName-用于将 Client 端连接到服务器集群的专用 GemFire 池的名称。该属性仅在应用程序是 GemFire 缓存 Client 端时使用。默认为gemfirePool

  • serverRegionShort-使用 GemFire RegionShortcut(默认值为PARTITION)指定 GemFire 的数据 ManagementPolicy。仅在配置服务器区域或使用 p2p 拓扑时才使用此属性。

Note

请务必注意,如果 Client 端区域是PROXYCACHING_PROXY,则 GemFireClient 端区域名称必须与服务器区域名称相同。如果用于存储 Spring 会话的 Client 端区域名称为LOCAL,则名称不需要匹配,但是请记住,您的会话状态不会传播到服务器,并且您失去了使用 GemFire 来存储和 Management 分布式复制会话的所有好处集群中的状态信息。

Note

serverRegionShort在 Client 端/服务器缓存配置中将被忽略,并且仅在对等(P2P)拓扑(更具体地说,使用 GemFire 对等缓存)时适用。

Server Configuration

我们仅涵盖了等式的一侧。我们还需要一个 GemFire 服务器,以便我们的 Client 端与之对话并向服务器发送会话状态进行 Management。

在此示例中,我们将使用以下 GemFire Server Java 配置:

@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 30) (1)
public class ServerConfig {

        static final int SERVER_PORT = 12480;

        static final String DEFAULT_GEMFIRE_LOG_LEVEL = "warning";
        static final String SERVER_HOST = "localhost";

        @SuppressWarnings("resource")
        public static void main(String[] args) throws IOException { (5)
                new AnnotationConfigApplicationContext(ServerConfig.class)
                        .registerShutdownHook();
        }

        @Bean
        static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
                return new PropertySourcesPlaceholderConfigurer();
        }

        Properties gemfireProperties() { (2)
                Properties gemfireProperties = new Properties();

                gemfireProperties.setProperty("name", applicationName());
                gemfireProperties.setProperty("mcast-port", "0");
                gemfireProperties.setProperty("log-level", logLevel());
                gemfireProperties.setProperty("jmx-manager", "true");
                gemfireProperties.setProperty("jmx-manager-start", "true");

                return gemfireProperties;
        }

        String applicationName() {
                return "samples:httpsession-gemfire-clientserver:"
                        .concat(getClass().getSimpleName());
        }

        String logLevel() {
                return System.getProperty("sample.httpsession.gemfire.log-level",
                        DEFAULT_GEMFIRE_LOG_LEVEL);
        }

        @Bean
        CacheFactoryBean gemfireCache() { (3)
                CacheFactoryBean gemfireCache = new CacheFactoryBean();

                gemfireCache.setClose(true);
                gemfireCache.setProperties(gemfireProperties());

                return gemfireCache;
        }

        @Bean
        CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache,
                        @Value("${spring.session.data.gemfire.port:" + SERVER_PORT + "}") int port) { (4)

                CacheServerFactoryBean gemfireCacheServer = new CacheServerFactoryBean();

                gemfireCacheServer.setAutoStartup(true);
                gemfireCacheServer.setBindAddress(SERVER_HOST);
                gemfireCacheServer.setCache(gemfireCache);
                gemfireCacheServer.setHostNameForClients(SERVER_HOST);
                gemfireCacheServer.setMaxTimeBetweenPings(Long.valueOf(TimeUnit.SECONDS.toMillis(60)).intValue());
                gemfireCacheServer.setPort(port);

                return gemfireCacheServer;
        }
}
  • (1) 在服务器上,我们还使用@EnableGemFireHttpSessionComments 配置 Spring Session。这样可以确保 Client 端和服务器上的 Region 名称都匹配(在此示例中,我们使用默认的“ * ClusteredSpringSessions *”)。我们还将会话超时设置为 30 秒 。稍后,我们将看到如何使用此超时。
  • (2) 接下来,我们使用 GemFire 系统属性配置 GemFire 服务器,就像我们的 P2P 示例一样。在mcast-port设置为 0 且未指定locators属性的情况下,我们的服务器将是独立的。我们还允许 JMXClient 端(例如* Gfsh *)通过使用特定于 GemFire 的 JMX 系统属性连接到我们的服务器。
  • (3) 然后,我们创建一个使用 GemFire 系统属性初始化的 GemFire 对等体Cache的实例。
  • (4) 我们还设置了一个在 localhost 上运行的 GemFire CacheServer实例,监听端口 12480 ,准备接受我们的 Client 端连接。
  • (5) 最后,我们声明一个main方法作为从命令行启动和运行 GemFire 服务器的入口点。
Java Servlet 容器初始化

我们的Spring Java 配置创建了一个实现Filter的名为springSessionRepositoryFilter的 Spring bean。 springSessionRepositoryFilter bean 负责用 Spring Session 和 GemFire 支持的自定义实现替换HttpSession

为了让我们的Filter发挥其魔力,Spring 需要加载我们的ClientConfig类。我们还需要确保我们的 Servlet 容器(即 Tomcat)对每个请求都使用springSessionRepositoryFilter。幸运的是,Spring Session 提供了一个名为AbstractHttpSessionApplicationInitializer的 Util 类,使这两个步骤都非常容易。

您可以在下面找到一个示例:

src/main/java/sample/Initializer.java

public class Initializer extends AbstractHttpSessionApplicationInitializer { (1)

        public Initializer() {
                super(ClientConfig.class); (2)
        }
}

Note

我们类的名称(Initializer)无关紧要。重要的是我们扩展AbstractHttpSessionApplicationInitializer

  • (1) 第一步是扩展AbstractHttpSessionApplicationInitializer。这确保了名为springSessionRepositoryFilter的 Spring bean 已在我们的 Servlet 容器中注册并用于每个请求。
  • (2) AbstractHttpSessionApplicationInitializer还提供了一种机制,可以轻松地让 Spring 加载ClientConfig
GemFireClient 端-服务器基于 XML 的配置

本节介绍如何使用 GemFire 的 Client 端-服务器拓扑结构支持基于 XML 的HttpSession配置。

Note

使用 XML 示例的 GemFire(Client 端-服务器)上的 HttpSession提供了一个工作示例,说明如何使用 XML 配置集成 Spring Session 和 GemFire 以替换HttpSession。您可以阅读下面的集成基本步骤,但在与自己的应用程序集成时,建议您使用 XML 指南以及带有 GemFire(Client 端-服务器)的详细 HttpSession 和 XML 指南。

Spring XML 配置

添加所需的依赖项和存储库声明后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器,以 Spring Session 和 GemFire 支持的实现替换HttpSession

添加以下 Spring 配置:

(1)
        <context:annotation-config/>

        (2)
        <context:property-placeholder location="classpath:META-INF/spring/application.properties"/>

        (3)
        <bean class="sample.GemFireCacheServerReadyBeanPostProcessor"/>

        (4)
        <util:properties id="gemfireProperties">
                <prop key="log-level">${sample.httpsession.gemfire.log-level:warning}</prop>
        </util:properties>

        (5)
        <gfe:client-cache properties-ref="gemfireProperties" pool-name="gemfirePool"/>

        (6)
        <gfe:pool keep-alive="false"
              ping-interval="5000"
              read-timeout="5000"
              retry-attempts="1"
              subscription-enabled="true"
              thread-local-connections="false">
                <gfe:server host="${application.gemfire.client-server.host}"
                    port="${spring.session.data.gemfire.port:${application.gemfire.client-server.port}}"/>
        </gfe:pool>

        (7)
        <bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
                  p:maxInactiveIntervalInSeconds="30" p:poolName="DEFAULT"/>
  • (1) 启用了带有<context:annotation-config/>元素的 SpringComments 配置支持,因此将对 XML 配置中声明的,使用 Spring 或 Spring 支持的标准 JavaComments 进行 Comments 的任何 Spring bean 进行适当配置。
  • (2) META-INF/spring/application.properties文件与PropertySourcesPlaceholderConfigurer bean 一起使用,以使用适当的属性值替换 Spring XML 配置元数据中的占位符。
  • (3) 然后注册了“ GemFireCacheSeverReadyBeanPostProcessor”,以确定指定主机/端口上的 GemFire 服务器是否正在运行并监听 Client 端连接,从而阻止 Client 端启动,直到服务器可用并就绪为止。
  • (4) 接下来,我们包含一个Properties bean,以使用GemFire 的系统属性配置 GemFireClient 端缓存的某些方面。在这种情况下,我们只是从应用程序特定的 System 属性设置 GemFire 的log-level,如果未指定则默认为warning
  • (5) 然后,我们创建一个 GemFire ClientCache实例,并以gemfireProperties初始化。
  • (6) 我们配置了一个 Client 端连接池,以在 Client 端/服务器拓扑中与 GemFire 服务器对话。在我们的配置中,我们对超时,连接数等使用了合理的设置。另外,我们的Pool已配置为直接连接到服务器。
  • (7) 最后,GemFireHttpSessionConfiguration已注册以启用 Spring Session 功能。

Tip

在群集可能包含数百个 GemFire 数据节点(服务器)的典型 GemFire 部署中,Client 端连接到群集中运行的一个或多个 GemFire Locator 更为常见。定位器将有关可用服务器,负载以及哪些服务器具有感兴趣的 Client 端数据的元数据传递给 Client 端,这对于单跳直接数据访问特别重要。查看有关GemFire 用户指南中的 Client 端/服务器拓扑的更多详细信息。

Note

有关配置* Spring Data GemFire *的更多信息,请参阅reference guide

Server Configuration

我们仅涵盖了等式的一侧。我们还需要一个 GemFire 服务器,以便我们的 Client 端与之对话并向服务器发送会话状态信息以进行 Management。

在此示例中,我们将使用以下 GemFire Server Java 配置:

(1)
        <context:annotation-config/>

        (2)
        <context:property-placeholder location="classpath:META-INF/spring/application.properties"/>

        (3)
        <util:properties id="gemfireProperties">
                <prop key="name">GemFireClientServerHttpSessionXmlSample</prop>
                <prop key="mcast-port">0</prop>
                <prop key="log-level">${sample.httpsession.gemfire.log-level:warning}</prop>
                <prop key="jmx-manager">true</prop>
                <prop key="jmx-manager-start">true</prop>
        </util:properties>

        (4)
        <gfe:cache properties-ref="gemfireProperties"/>

        (5)
        <gfe:cache-server auto-startup="true"
                      bind-address="${application.gemfire.client-server.host}"
                      host-name-for-clients="${application.gemfire.client-server.host}"
                      port="${spring.session.data.gemfire.port:${application.gemfire.client-server.port}}"/>

        (6)
        <bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
                  p:maxInactiveIntervalInSeconds="30"/>
  • (1) 首先,我们使用<context:annotation-config>元素启用 SpringComments 配置支持,以便在 XML 配置中声明的,使用 Spring 或 Spring 支持的 Spring 或 Standard JavaComments 进行 Comments 的任何 Spring bean 都将得到适当配置。
  • (2) 已注册PropertySourcesPlaceholderConfigurer,以将我们的 Spring XML 配置元数据中的占位符替换为META-INF/spring/application.properties文件中的属性值。
  • (3) 接下来,我们使用 GemFire 系统属性配置 GemFire 服务器,就像我们的 P2P 示例一样。在mcast-port设置为 0 且未指定locators属性的情况下,我们的服务器将是独立的。我们还允许 JMXClient 端(例如* Gfsh *)通过使用特定于 GemFire 的 JMX 系统属性连接到我们的服务器。
  • (4) 然后,我们创建一个 GemFire 对等体Cache的实例,该实例使用 GemFire 系统属性进行了初始化。
  • (5) 我们还设置了一个在 localhost 上运行的 GemFire CacheServer实例,监听端口 11235 ,准备接受我们的 Client 端连接。
  • (6) 最后,我们通过注册GemFireHttpSessionConfiguration实例来启用与 Client 端相同的 Spring Session 功能,除了我们将会话过期超时设置为 30 秒 。稍后我们将解释这意味着什么。

GemFire 服务器配置通过以下方式进行引导:

@Configuration (1)
@ImportResource("META-INF/spring/session-server.xml") (2)
public class Application {

        public static void main(final String[] args) {
                new AnnotationConfigApplicationContext(Application.class)
                        .registerShutdownHook();
        }
}

Tip

除了使用 main 方法创建简单的 Java 类之外,还可以使用* Spring Boot *。

  • (1) @ConfigurationComments 使用 Spring 的 Comments 配置支持将此 Java 类指定为 Spring 配置元数据的源。
  • (2) 最初,配置来自META-INF/spring/session-server.xml文件,这也是此示例中未使用* Spring Boot *的原因,因为使用 XML 似乎无法使用 Spring Boot 的目的和优点。但是,此 samples 是关于演示如何使用 Spring XML 来配置 GemFireClient 端和服务器的。
XML Servlet 容器初始化

我们的Spring XML 配置创建了一个实现Filter的名为springSessionRepositoryFilter的 Spring bean。 springSessionRepositoryFilter bean 负责用由 Spring Session 和 GemFire 支持的自定义实现替换HttpSession

为了使Filter发挥其魔力,我们需要指示 Spring 加载session-client.xml配置文件。我们使用以下配置进行此操作:

src/main/webapp/WEB-INF/web.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/session-client.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

ContextLoaderListener读取contextConfigLocation上下文参数值,并获取我们的* session-client.xml *配置文件。

最后,我们需要确保我们的 Servlet 容器(即 Tomcat)对每个请求都使用springSessionRepositoryFilter

以下代码段为我们执行了最后一步:

src/main/webapp/WEB-INF/web.xml

<filter>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

DelegatingFilterProxy将查找名称为springSessionRepositoryFilter的 bean,并将其转换为Filter。对于每个调用DelegatingFilterProxy的请求,都将调用springSessionRepositoryFilter

GemFire 对等(P2P)

也许不太常见的是使用Peer-To-Peer (P2P)拓扑将 Spring Session 应用程序配置为 GemFire 集群中的对等成员。在这种配置中,Spring Session 应用程序将是 GemFire 集群中的实际数据节点(服务器),并且不是以前的缓存 Client 端。

这种方法的一个优点是应用程序接近应用程序的状态(即它的数据)。但是,还有其他有效的方法可以完成类似的数据相关计算,例如使用 GemFire 的Function Execution。当 GemFire 充当 Spring Session 中的提供者时,可以使用 GemFire 的其他features中的任何一个。

P2P 对于测试目的以及更小,更集中且自包含的应用程序(例如微服务体系结构中的应用程序)都非常有用,并且肯定会改善应用程序的延迟,吞吐量和一致性需求。

您可以使用以下任一配置对等(P2P)拓扑:

基于 Java 的 GemFire 对等(P2P)配置

本节介绍如何使用 GemFire 的对等(P2P)拓扑来使用基于 Java 的配置来支持HttpSession

Note

带有 GemFire 的 HttpSession(P2P)示例提供了有关如何使用 Java 配置集成 Spring Session 和 GemFire 以替换HttpSession的工作示例。您可以阅读下面的集成基本步骤,但是在与自己的应用程序集成时,建议您遵循详细的 HttpSession with GemFire(P2P)指南。

Spring Java 配置

添加所需的依赖项和存储库声明后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器,以 Spring Session 和 GemFire 支持的实现替换HttpSession

添加以下 Spring 配置:

@EnableGemFireHttpSession (1)
public class Config {

        @Bean
        Properties gemfireProperties() { (2)
                Properties gemfireProperties = new Properties();

                gemfireProperties.setProperty("name", "GemFireP2PHttpSessionSample");
                gemfireProperties.setProperty("mcast-port", "0");
                gemfireProperties.setProperty("log-level",
                                System.getProperty("sample.httpsession.gemfire.log-level", "warning"));
                gemfireProperties.setProperty("jmx-manager", "true");
                gemfireProperties.setProperty("jmx-manager-start", "true");

                return gemfireProperties;
        }

        @Bean
        CacheFactoryBean gemfireCache() { (3)
                CacheFactoryBean gemfireCache = new CacheFactoryBean();

                gemfireCache.setClose(true);
                gemfireCache.setProperties(gemfireProperties());

                return gemfireCache;
        }
}
  • (1) @EnableGemFireHttpSessionComments 创建一个实现Filter的名为springSessionRepositoryFilter的 Spring bean。过滤器将HttpSession替换为 Spring Session 支持的实现。在这种情况下,Spring Session 由 GemFire 支持。
  • (2) 然后,我们使用标准 GemFire 系统属性配置 GemFire 对等缓存。我们使用name属性为 GemFire 数据节点命名,并将mcast-port设置为 0.如果没有locators属性,则该数据节点将是独立服务器。 GemFire 的log-level是使用特定于应用程序的系统属性(sample.httpsession.gemfire.log-level)设置的,用户可以在使用 Maven 或 Gradle 运行此示例应用程序时在命令行上指定该属性(默认值为“ * warning *”)。
  • (3) 最后,我们创建 GemFire 对等缓存的实例,该实例将 GemFire 嵌入与正在运行的 Spring Session 示例应用程序相同的 JVM 进程中。

Tip

此外,我们还使用特定于 GemFire 的 JMX 系统属性将该数据节点(服务器)配置为 GemFireManagement 器,该属性使 JMXClient 端(例如* Gfsh *)能够连接到此运行的数据节点。

Note

有关配置* Spring Data GemFire *的更多信息,请参阅reference guide

@EnableGemFireHttpSessionComments 使开发人员可以使用以下属性立即配置 Spring Session 和 GemFire 的某些方面:

  • maxInactiveIntervalInSeconds-控制 HttpSession 空闲超时时间(默认为 30 分钟 )。

  • regionName-指定用于存储HttpSession状态的 GemFire 区域的名称(默认值为“ * ClusteredSpringSessions *”)。

  • serverRegionShort-用 GemFire RegionShortcut指定 GemFire 数据 ManagementPolicy(默认值为PARTITION)。

Note

clientRegionShort在对等缓存配置中将被忽略,并且仅在 Client 端-服务器拓扑(更具体地说,是使用 GemFireClient 端缓存)时适用。

Java Servlet 容器初始化

我们的Spring Java 配置创建了一个实现Filter的名为springSessionRepositoryFilter的 Spring bean。 springSessionRepositoryFilter bean 负责用 Spring Session 和 GemFire 支持的自定义实现替换HttpSession

为了让我们的Filter发挥其魔力,Spring 需要加载我们的Config类。我们还需要确保我们的 Servlet 容器(即 Tomcat)对每个请求都使用springSessionRepositoryFilter。幸运的是,Spring Session 提供了一个名为AbstractHttpSessionApplicationInitializer的 Util 类,使这两个步骤都非常容易。

您可以在下面找到一个示例:

src/main/java/sample/Initializer.java

public class Initializer extends AbstractHttpSessionApplicationInitializer { (1)

        public Initializer() {
                super(Config.class); (2)
        }
}

Note

我们类的名称(Initializer)无关紧要。重要的是我们扩展AbstractHttpSessionApplicationInitializer

  • (1) 第一步是扩展AbstractHttpSessionApplicationInitializer。这确保了名为springSessionRepositoryFilter的 Spring bean 已在我们的 Servlet 容器中注册并用于每个请求。
  • (2) AbstractHttpSessionApplicationInitializer还提供了一种机制,可以轻松地让 Spring 加载Config
GemFire 对等(P2P)基于 XML 的配置

本节介绍如何使用 GemFire 的对等(P2P)拓扑来使用基于 XML 的配置来支持HttpSession

Note

使用 XML 示例的带有 GemFire(P2P)的 HttpSession提供了一个工作示例,说明如何使用 XML 配置集成 Spring Session 和 GemFire 以替换HttpSession。您可以阅读下面的集成基本步骤,但是在与自己的应用程序集成时,建议您使用 XML 指南以及带有 GemFire(P2P)的详细 HttpSession 进行遵循。

Spring XML 配置

添加所需的依赖项和存储库声明后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器,以 Spring Session 和 GemFire 支持的实现替换HttpSession

添加以下 Spring 配置:

src/main/webapp/WEB-INF/spring/session.xml

(1)
<context:annotation-config/>
<context:property-placeholder/>

<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"/>

(2)
<util:properties id="gemfireProperties">
    <prop key="name">GemFireP2PHttpSessionXmlSample</prop>
    <prop key="mcast-port">0</prop>
    <prop key="log-level">${sample.httpsession.gemfire.log-level:warning}</prop>
    <prop key="jmx-manager">true</prop>
    <prop key="jmx-manager-start">true</prop>
</util:properties>

(3)
<gfe:cache properties-ref="gemfireProperties" use-bean-factory-locator="false"/>
  • (1) 我们使用<context:annotation-config/>GemFireHttpSessionConfiguration的组合,因为 Spring Session 尚未提供 XML 命名空间支持(请参阅gh-104)。这将创建一个名称为springSessionRepositoryFilter的 Spring Bean,该 Bean 实现Filter。过滤器是用 Spring Session 支持的实现替换HttpSession的工具。在这种情况下,Spring Session 由 GemFire 支持。
  • (2) 然后,我们使用标准 GemFire 系统属性配置 GemFire 对等缓存。我们使用name属性为 GemFire 数据节点命名,并将mcast-port设置为 0.如果没有locators属性,则该数据节点将是独立服务器。 GemFire 的log-level是使用特定于应用程序的系统属性(sample.httpsession.gemfire.log-level)设置的,用户可以在使用 Maven 或 Gradle 运行此应用程序时在命令行上指定该属性(默认值为“ * warning *”)。
  • (3) 最后,我们创建 GemFire 对等缓存的实例,该实例将 GemFire 嵌入与正在运行的 Spring Session 示例应用程序相同的 JVM 进程中。

Tip

此外,我们还使用特定于 GemFire 的 JMX 系统属性将该数据节点(服务器)配置为 GemFireManagement 器,该属性使 JMXClient 端(例如* Gfsh *)能够连接到此运行的数据节点。

Note

有关配置* Spring Data GemFire *的更多信息,请参阅reference guide

XML Servlet 容器初始化

我们的Spring XML 配置创建了一个实现Filter的名为springSessionRepositoryFilter的 Spring bean。 springSessionRepositoryFilter bean 负责用由 Spring Session 和 GemFire 支持的自定义实现替换HttpSession

为了使Filter发挥神奇的作用,我们需要指示 Spring 加载session.xml配置文件。我们使用以下配置进行此操作:

src/main/webapp/WEB-INF/web.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/spring/*.xml
    </param-value>
</context-param>
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

ContextLoaderListener读取contextConfigLocation上下文参数值,并获取我们的* session.xml *配置文件。

最后,我们需要确保我们的 Servlet 容器(即 Tomcat)对每个请求都使用springSessionRepositoryFilter

以下代码段为我们执行了最后一步:

src/main/webapp/WEB-INF/web.xml

<filter>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

DelegatingFilterProxy将查找名称为springSessionRepositoryFilter的 bean,并将其转换为Filter。对于每个调用DelegatingFilterProxy的请求,都将调用springSessionRepositoryFilter

具有 JDBC 的 HttpSession

通过在使用HttpSession的任何内容之前添加一个 Servlet 过滤器来启用将 Spring Session 与HttpSession一起使用。您可以使用以下任一方法来启用此功能:

基于 JDBC Java 的配置

本节描述如何使用关系数据库通过基于 Java 的配置来支持HttpSession

Note

HttpSession JDBC 示例提供了有关如何使用 Java 配置集成 Spring Session 和HttpSession的工作示例。您可以阅读下面的集成基本步骤,但是建议您在与自己的应用程序集成时遵循详细的 HttpSession JDBC 指南。

Spring Java 配置

添加所需的依赖关系后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器,该过滤器将HttpSession实现替换为 Spring Session 支持的实现。添加以下 Spring 配置:

@EnableJdbcHttpSession (1)
public class Config {

        @Bean
        public EmbeddedDatabase dataSource() {
                return new EmbeddedDatabaseBuilder() (2)
                                .setType(EmbeddedDatabaseType.H2)
                                .addScript("org/springframework/session/jdbc/schema-h2.sql").build();
        }

        @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource) {
                return new DataSourceTransactionManager(dataSource); (3)
        }

}
  • (1) @EnableJdbcHttpSessionComments 创建一个名称为springSessionRepositoryFilter的 Spring Bean,该 Bean 实现了 Filter。过滤器负责替换由 Spring Session 支持的HttpSession实现。在这种情况下,Spring Session 由关系数据库支持。
  • (2) 我们创建一个dataSource,将 Spring Session 连接到 H2 数据库的嵌入式实例。我们将 H2 数据库配置为使用 Spring Session 中包含的 SQL 脚本创建数据库表。
  • (3) 我们创建一个transactionManager来 Management 先前配置的dataSource的 Transaction。

有关如何配置与数据访问相关的问题的更多信息,请参阅Spring 框架参考文档

Java Servlet 容器初始化

我们的Spring Configuration创建了一个名为springSessionRepositoryFilter的 Spring Bean,它实现了FilterspringSessionRepositoryFilter bean 负责用 Spring Session 支持的自定义实现替换HttpSession

为了让我们的Filter发挥其魔力,Spring 需要加载我们的Config类。最后,我们需要确保我们的 Servlet 容器(即 Tomcat)对每个请求都使用springSessionRepositoryFilter。幸运的是,Spring Session 提供了一个名为AbstractHttpSessionApplicationInitializer的 Util 类,这两个步骤都非常容易。您可以在下面找到一个示例:

src/main/java/sample/Initializer.java

public class Initializer extends AbstractHttpSessionApplicationInitializer { (1)

        public Initializer() {
                super(Config.class); (2)
        }
}

Note

我们的类(Initializer)的名称无关紧要。重要的是我们扩展AbstractHttpSessionApplicationInitializer

  • (1) 第一步是扩展AbstractHttpSessionApplicationInitializer。这可以确保为每个请求向我们的 Servlet 容器注册名称为springSessionRepositoryFilter的 Spring Bean。
  • (2) AbstractHttpSessionApplicationInitializer还提供了一种机制,可以轻松确保 Spring 加载我们的Config

基于 JDBC XML 的配置

本节描述如何使用关系数据库通过基于 XML 的配置来支持HttpSession

Note

HttpSession JDBC XML 示例提供了有关如何使用 XML 配置集成 Spring Session 和HttpSession的工作示例。您可以阅读下面的集成基本步骤,但是在与自己的应用程序集成时,建议您遵循详细的 HttpSession JDBC XML 指南。

Spring XML 配置

添加所需的依赖关系之后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器,该过滤器将HttpSession实现替换为 Spring Session 支持的实现。添加以下 Spring 配置:

src/main/webapp/WEB-INF/spring/session.xml

(1)
<context:annotation-config/>
<bean class="org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration"/>

(2)
<jdbc:embedded-database id="dataSource" database-name="testdb" type="H2">
    <jdbc:script location="classpath:org/springframework/session/jdbc/schema-h2.sql"/>
</jdbc:embedded-database>

(3)
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg ref="dataSource"/>
</bean>
  • (1) 我们使用<context:annotation-config/>JdbcHttpSessionConfiguration的组合,因为 Spring Session 尚未提供 XML 命名空间支持(请参阅gh-104)。这将创建一个名称为springSessionRepositoryFilter的 Spring Bean,它实现了 Filter。过滤器负责替换由 Spring Session 支持的HttpSession实现。在这种情况下,Spring Session 由关系数据库支持。
  • (2) 我们创建一个dataSource,该dataSource将 Spring Session 连接到 H2 数据库的嵌入式实例。我们将 H2 数据库配置为使用 Spring Session 中包含的 SQL 脚本创建数据库表。
  • (3) 我们创建一个transactionManager来 Management 先前配置的dataSource的 Transaction。

有关如何配置与数据访问相关的问题的更多信息,请参阅Spring 框架参考文档

XML Servlet 容器初始化

我们的Spring Configuration创建了一个实现Filter的名为springSessionRepositoryFilter的 Spring Bean。 springSessionRepositoryFilter bean 负责用 Spring Session 支持的自定义实现替换HttpSession

为了使Filter发挥其魔力,我们需要指示 Spring 加载session.xml配置。我们使用以下配置进行此操作:

src/main/webapp/WEB-INF/web.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/spring/*.xml
    </param-value>
</context-param>
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

ContextLoaderListener读取 contextConfigLocation 并选择我们的 session.xml 配置。

最后,我们需要确保我们的 Servlet 容器(即 Tomcat)对每个请求都使用springSessionRepositoryFilter。以下代码段为我们执行了最后一步:

src/main/webapp/WEB-INF/web.xml

<filter>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

DelegatingFilterProxy将查找名称为springSessionRepositoryFilter的 Bean 并将其转换为Filter。对于每个调用DelegatingFilterProxy的请求,都将调用springSessionRepositoryFilter

基于 JDBC Spring Boot 的配置

本节描述了使用 Spring Boot 时如何使用关系数据库来备份HttpSession

Note

HttpSession JDBC Spring Boot 示例提供了有关如何使用 Spring Boot 集成 Spring Session 和HttpSession的工作示例。您可以阅读下面的集成基本步骤,但是在与自己的应用程序集成时,建议您遵循详细的 HttpSession JDBC Spring Boot Guide。

Spring Configuration

添加所需的依赖关系之后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器,该过滤器将HttpSession实现替换为 Spring Session 支持的实现。添加以下 Spring 配置:

@EnableJdbcHttpSession (1)
public class HttpSessionConfig {
}
  • (1) @EnableJdbcHttpSessionComments 创建一个名称为springSessionRepositoryFilter的 Spring Bean,用于实现 Filter。过滤器负责替换由 Spring Session 支持的HttpSession实现。在这种情况下,Spring Session 由关系数据库支持。
配置数据源

Spring Boot 自动创建一个DataSource来将 Spring Session 连接到 H2 数据库的嵌入式实例。在生产环境中,您需要确保更新配置以指向关系数据库。例如,您可以在 application.properties 中包括以下内容

src/main/resources/application.properties

spring.datasource.url=jdbc:postgresql://localhost:5432/myapp
spring.datasource.username=myapp
spring.datasource.password=secret

有关更多信息,请参阅 Spring Boot 文档的配置数据源部分。

Servlet 容器初始化

我们的Spring Configuration创建了一个实现Filter的名为springSessionRepositoryFilter的 Spring Bean。 springSessionRepositoryFilter bean 负责用 Spring Session 支持的自定义实现替换HttpSession

为了让我们的Filter发挥其魔力,Spring 需要加载我们的Config类。最后,我们需要确保我们的 Servlet 容器(即 Tomcat)对每个请求都使用springSessionRepositoryFilter。幸运的是,Spring Boot 为我们完成了这两个步骤。

与 Mongo 进行 HttpSession

通过在使用HttpSession的任何内容之前添加一个 Servlet 过滤器来启用将 Spring Session 与HttpSession一起使用。

本节介绍如何使用 Mongo 通过基于 Java 的配置来支持HttpSession

Note

HttpSession Mongo 示例提供了有关如何使用 Java 配置集成 Spring Session 和HttpSession的工作示例。您可以阅读下面的集成基本步骤,但是建议您在与自己的应用程序集成时遵循详细的 HttpSession 指南。

您所要做的就是添加以下 Spring Configuration:

@EnableMongoHttpSession (1)
public class HttpSessionConfig {

        @Bean
        public JdkMongoSessionConverter jdkMongoSessionConverter() {
                return new JdkMongoSessionConverter(); (2)
        }
}
  • (1) @EnableMongoHttpSessionComments 创建一个名称为springSessionRepositoryFilter的 Spring Bean,用于实现 Filter。过滤器负责替换由 Spring Session 支持的HttpSession实现。在这种情况下,Spring Session 由 Mongo 支持。
  • (2) 我们明确配置JdkMongoSessionConverter,因为无法使用 Jackson 来自动持久化 Spring Security 的对象(如果 Jackson 在 Classpath 中,则为默认值)。

会话序列化机制

为了能够将会话对象持久存储在 MongoDB 中,我们需要提供序列化/反序列化机制。根据您的 Classpath,Spring Session 将选择以下两个内置转换器之一:

  • JacksonMongoSessionConverter ObjectMapper班有空,或者

  • JdkMongoSessionConverter否则。

JacksonMongoSessionConverter

该机制使用 Jackson 来将会话对象与 JSON 序列化。当在 Classpath 中检测到 Jackson 且用户未明确注册AbstractMongoSessionConverter Bean 时,JacksonMongoSessionConverter将是默认值。

如果您想提供自定义的 Jackson 模块,可以通过显式注册JacksonMongoSessionConverter来完成:

@Configuration
@EnableMongoHttpSession
public class MongoJacksonSessionConfiguration {

        @Bean
        public AbstractMongoSessionConverter mongoSessionConverter() {
                return new JacksonMongoSessionConverter(getJacksonModules());
        }

        public Iterable<Module> getJacksonModules() {
                return Collections.<Module>singletonList(new MyJacksonModule());
        }
}

JdkMongoSessionConverter

JdkMongoSessionConverter使用标准 Java 序列化将会话属性 Map 以二进制形式持久化到 MongoDB。但是,标准会话元素(例如 id,访问时间等)仍被编写为普通的 Mongo 对象,并且无需额外的努力即可读取和查询。如果 Jackson 不在 Classpath 上并且未定义显式AbstractMongoSessionConverter Bean,则使用JdkMongoSessionConverter。您可以通过将JdkMongoSessionConverter定义为 Bean 来显式注册。

@Configuration
@EnableMongoHttpSession
public class MongoJdkSessionConfiguration {

        @Bean
        public AbstractMongoSessionConverter mongoSessionConverter() {
                return new JdkMongoSessionConverter();
        }
}

还有一个带有SerializerDeserializer对象的构造函数,允许您传递自定义实现,当您要使用非默认类加载器时,这一点尤其重要。

使用自定义转换器

您可以通过扩展AbstractMongoSessionConverter类来创建自己的会话转换器。该实现将用于序列化,反序列化对象以及提供查询以访问会话。

带有 Hazelcast 的 HttpSession

通过在使用HttpSession的任何内容之前添加一个 Servlet 过滤器来启用将 Spring Session 与HttpSession一起使用。

本节介绍如何使用 Hazelcast 通过基于 Java 的配置来备份HttpSession

Note

榛果 Springsample提供了有关如何使用 Java 配置集成 Spring Session 和HttpSession的工作示例。您可以阅读下面的集成基本步骤,但是建议您在与自己的应用程序集成时遵循详细的《 Hazelcast Spring 指南》。

Spring Configuration

添加所需的依赖关系之后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器,该过滤器将HttpSession实现替换为 Spring Session 支持的实现。添加以下 Spring 配置:

@EnableHazelcastHttpSession (1)
@Configuration
public class HazelcastHttpSessionConfig {

        @Bean
        public HazelcastInstance hazelcastInstance() {
                MapAttributeConfig attributeConfig = new MapAttributeConfig()
                                .setName(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
                                .setExtractor(PrincipalNameExtractor.class.getName());

                Config config = new Config();

                config.getMapConfig("spring:session:sessions") (2)
                                .addMapAttributeConfig(attributeConfig)
                                .addMapIndexConfig(new MapIndexConfig(
                                                HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));

                return Hazelcast.newHazelcastInstance(config); (3)
        }

}
  • (1) @EnableHazelcastHttpSessionComments 创建一个名称为springSessionRepositoryFilter的 Spring Bean,用于实现 Filter。过滤器负责替换由 Spring Session 支持的HttpSession实现。在这种情况下,Spring Session 由 Hazelcast 支持。
  • (2) 为了支持通过主体名称索引检索会话,需要注册适当的ValueExtractor。 Spring Session 为此提供了PrincipalNameExtractor
  • (3) 我们创建了一个HazelcastInstance,用于将 Spring Session 与 Hazelcast 连接起来。默认情况下,Hazelcast 的嵌入式实例是由应用程序启动并连接的。有关配置 Hazelcast 的更多信息,请参阅reference documentation

Servlet 容器初始化

我们的Spring Configuration创建了一个实现Filter的名为springSessionRepositoryFilter的 Spring Bean。 springSessionRepositoryFilter bean 负责用 Spring Session 支持的自定义实现替换HttpSession

为了让我们的Filter发挥其魔力,Spring 需要加载我们的SessionConfig类。由于我们的应用程序已经在使用SecurityInitializer类加载 Spring 配置,因此我们可以简单地将SessionConfig类添加到其中。

src/main/java/sample/SecurityInitializer.java

public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {

        public SecurityInitializer() {
                super(SecurityConfig.class, SessionConfig.class);
        }
}

最后,我们需要确保我们的 Servlet 容器(即 Tomcat)对每个请求都使用springSessionRepositoryFilter。在 Spring Security 的springSecurityFilterChain之前调用 Spring Session 的springSessionRepositoryFilter极为重要。这样可以确保 Spring Security 支持HttpSession。幸运的是,Spring Session 提供了一个名为AbstractHttpSessionApplicationInitializer的 Util 类,这使此操作非常容易。您可以在下面找到一个示例:

src/main/java/sample/Initializer.java

public class Initializer extends AbstractHttpSessionApplicationInitializer {

}

Note

我们的类(Initializer)的名称无关紧要。重要的是我们扩展AbstractHttpSessionApplicationInitializer

通过扩展AbstractHttpSessionApplicationInitializer,我们确保在 Spring Security 的springSecurityFilterChain之前为每个请求向我们的 Servlet 容器注册名称为springSessionRepositoryFilter的 Spring Bean。

HttpSession 集成如何工作

幸运的是HttpSessionHttpServletRequest(用于获取HttpSession的 API)都是接口。这意味着我们可以为每个这些 API 提供自己的实现。

Note

本节描述 Spring Session 如何提供与HttpSession的透明集成。目的是使用户可以了解幕后情况。此功能已经集成,您无需自己实现此逻辑。

首先,我们创建一个自定义HttpServletRequest,它返回HttpSession的自定义实现。它看起来像以下内容:

public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {

        public SessionRepositoryRequestWrapper(HttpServletRequest original) {
                super(original);
        }

        public HttpSession getSession() {
                return getSession(true);
        }

        public HttpSession getSession(boolean createNew) {
                // create an HttpSession implementation from Spring Session
        }

        // ... other methods delegate to the original HttpServletRequest ...
}

任何返回HttpSession的方法都将被覆盖。所有其他方法都由HttpServletRequestWrapper实现,并仅委托给原始HttpServletRequest实现。

我们使用名为SessionRepositoryFilter的 servlet Filter替换HttpServletRequest实现。伪代码可以在下面找到:

public class SessionRepositoryFilter implements Filter {

        public doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
                HttpServletRequest httpRequest = (HttpServletRequest) request;
                SessionRepositoryRequestWrapper customRequest =
                        new SessionRepositoryRequestWrapper(httpRequest);

                chain.doFilter(customRequest, response, chain);
        }

        // ...
}

通过将自定义HttpServletRequest实现传递到FilterChain中,我们确保Filter之后调用的所有内容都使用自定义HttpSession实现。这突出了为什么必须将 Spring Session 的SessionRepositoryFilter放置在与HttpSession进行交互的任何内容之前很重要。

在单个浏览器中有多个 HttpSession

Spring Session 能够在单个浏览器实例中支持多个会话。这提供了在同一浏览器实例(即 Google 帐户)中支持对多个用户进行身份验证的能力。

Note

Management 多个用户指南提供了在同一浏览器实例中 Management 多个用户的完整工作示例。您可以按照下面的基本集成步骤进行操作,但是在与自己的应用程序集成时,建议您遵循详细的《Management 多个用户指南》。

让我们看一下 Spring Session 如何跟踪多个会话。

Management 单个会话

Spring Session 通过将值添加到名为 SESSION 的 cookie 中来跟踪HttpSession。例如,SESSION cookie 的值可能为:

7e8383a4-082c-4ffe-a4bc-c40fd3363c5e

添加会话

我们可以通过请求一个包含特殊参数的 URL 来添加另一个会话。默认情况下,参数名称为 _ s 。例如,以下 URL 将创建一个新会话:

http://localhost:8080/?_s=1

Note

该参数值未指示实际的会话 ID。这很重要,因为我们永远不想让 Client 端确定会话 ID 以避免会话固定攻击。此外,我们不希望会话 ID 泄漏,因为它是作为查询参数发送的。请记住,敏感信息应仅作为 Headers 或在请求正文中传输。

除了自己创建 URL,我们还可以利用HttpSessionManager为我们完成此操作。我们可以使用以下方法从HttpServletRequest获得HttpSessionManager

src/main/java/sample/UserAccountsFilter.java

HttpSessionManager sessionManager = (HttpSessionManager) httpRequest
        .getAttribute(HttpSessionManager.class.getName());

现在,我们可以使用它来创建 URL 以添加另一个会话。

src/main/java/sample/UserAccountsFilter.java

String addAlias = unauthenticatedAlias == null ? (1)
        sessionManager.getNewSessionAlias(httpRequest)
        : (2)
        unauthenticatedAlias; (3)
String addAccountUrl = sessionManager.encodeURL(contextPath, addAlias); (4)
  • (1) 我们有一个名为unauthenticatedAlias的现有变量。该值是一个别名,它指向现有的未经身份验证的会话。如果不存在这样的会话,则该值为 null。这可以确保我们是否有一个现有的未经身份验证的会话使用它,而不是创建一个新的会话。
  • (2) 如果我们所有会话均已与用户相关联,则我们将创建一个新的会话别名。
  • (3) 如果存在与用户无关的会话,我们将使用其会话别名。
  • (4) 最后,我们创建添加帐户 URL。该 URL 包含一个会话别名,该会话别名要么指向现有的未经身份验证的会话,要么是一个未使用的别名,从而发出 signal 以创建与该别名相关联的新会话。

现在我们的 SESSION Cookie 看起来像这样:

0 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e 1 1d526d4a-c462-45a4-93d9-84a39b6d44ad

Such that:

  • 会话 ID 为 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e

  • 该会话的别名为 0 。例如,如果 URL 为http://localhost:8080/?_s=0,则将使用此别名。

    • 这是默认会话。这意味着,如果未指定会话别名,则使用此会话。例如,如果 URL 为http://localhost:8080/,则将使用此会话。
  • 会话 ID 为 1d526d4a-c462-45a4-93d9-84a39b6d44ad

  • 此会话的别名为 1 。如果会话别名为 1 ,则使用此会话。例如,如果 URL 为http://localhost:8080/?_s=1,则将使用此别名。

自动会话别名包含 encodeURL

在 URL 中指定会话别名的好处是,我们可以打开多个带有不同活动会话的选项卡。不好的是,我们需要在应用程序的每个 URL 中都包含会话别名。幸运的是,Spring Session 会在通过HttpServletResponse#encodeURL(java.lang.String)传递的任何 URL 中自动包含会话别名。

这意味着,如果您使用的是标准标记库,则会话别名将自动包含在 URL 中。例如,如果我们当前正在使用别名为 1 的会话,则执行以下操作:

src/main/webapp/index.jsp

-->
<c:url value="/link.jsp" var="linkUrl"/>
<a id="navLink" href="${linkUrl}">Link</a>

将输出以下链接:

<a id="navLink" href="/link.jsp?_s=1">Link</a>

HttpSession 和 RESTful API

通过允许在 Headers 中提供会话,Spring Session 可以与 RESTful API 一起使用。

Note

REST Sample提供了有关如何在 REST 应用程序中使用 Spring Session 来支持 Headers 身份验证的工作示例。您可以按照以下集成的基本步骤进行操作,但是在与自己的应用程序集成时,建议您遵循详细的 REST 指南。

Spring Configuration

添加所需的依赖关系之后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器,该过滤器将HttpSession实现替换为 Spring Session 支持的实现。添加以下 Spring 配置:

@Configuration
@EnableRedisHttpSession (1)
public class HttpSessionConfig {

        @Bean
        public LettuceConnectionFactory connectionFactory() {
                return new LettuceConnectionFactory(); (2)
        }

        @Bean
        public HttpSessionStrategy httpSessionStrategy() {
                return new HeaderHttpSessionStrategy(); (3)
        }
}
  • (1) @EnableRedisHttpSessionComments 创建一个名称为springSessionRepositoryFilter的 Spring Bean,实现Filter。过滤器负责替换由 Spring Session 支持的HttpSession实现。在这种情况下,Spring Session 由 Redis 支持。
  • (2) 我们创建一个RedisConnectionFactory,将 Spring Session 连接到 Redis Server。我们将连接配置为在默认端口(6379)上连接到 localhost。有关配置 Spring Data Redis 的更多信息,请参阅reference documentation
  • (3) 我们自定义了 Spring Session 的 HttpSession 集成,以使用 HTTP Headers 来传达当前会话信息,而不是 cookie。

Servlet 容器初始化

我们的Spring Configuration创建了一个实现Filter的名为springSessionRepositoryFilter的 Spring Bean。 springSessionRepositoryFilter bean 负责用 Spring Session 支持的自定义实现替换HttpSession

为了让我们的Filter发挥其魔力,Spring 需要加载我们的Config类。我们在 Spring MvcInitializer中提供配置,如下所示:

src/main/java/sample/mvc/MvcInitializer.java

@Override
protected Class<?>[] getRootConfigClasses() {
    return new Class[] { SecurityConfig.class, HttpSessionConfig.class };
}

最后,我们需要确保我们的 Servlet 容器(即 Tomcat)对每个请求都使用springSessionRepositoryFilter。幸运的是,Spring Session 提供了一个名为AbstractHttpSessionApplicationInitializer的 Util 类,这使得此操作非常容易。只需使用默认构造函数扩展该类,如下所示:

src/main/java/sample/Initializer.java

public class Initializer extends AbstractHttpSessionApplicationInitializer {

}

Note

我们的类(Initializer)的名称无关紧要。重要的是我们扩展AbstractHttpSessionApplicationInitializer

HttpSessionListener

Spring Session pass 语句SessionEventHttpSessionListenerAdapterSessionDestroyedEventSessionCreatedEvent转换为HttpSessionEvent来支持HttpSessionListener。要使用此支持,您需要:

  • 确保您的SessionRepository实现支持并配置为触发SessionDestroyedEventSessionCreatedEvent

  • SessionEventHttpSessionListenerAdapter配置为 Spring bean。

  • 将每个HttpSessionListener注入SessionEventHttpSessionListenerAdapter

如果您使用的是带有 Redis 的 HttpSession中记录的配置支持,则只需将每个HttpSessionListener注册为 Bean。例如,假设您要支持 Spring Security 的并发控制,并且需要使用HttpSessionEventPublisher,则只需将HttpSessionEventPublisher添加为 bean。在 Java 配置中,这可能类似于:

@Configuration
@EnableRedisHttpSession
public class RedisHttpSessionConfig {

        @Bean
        public HttpSessionEventPublisher httpSessionEventPublisher() {
                return new HttpSessionEventPublisher();
        }

        // ...
}

在 XML 配置中,这可能类似于:

<bean class="org.springframework.security.web.session.HttpSessionEventPublisher"/>

WebSocket Integration

Spring Session 提供了与 Spring 的 WebSocket 支持的透明集成。

Note

Spring Session 的 WebSocket 支持仅与 Spring 的 WebSocket 支持一起使用。具体来说,它不能直接与JSR-356一起使用。这是由于 JSR-356 没有用于拦截传入的 WebSocket 消息的机制。

为什么使用 Spring Session 和 WebSockets?

那么,为什么在使用 WebSockets 时需要 Spring Session?

考虑一个通过 HTTP 请求完成其大部分工作的电子邮件应用程序。但是,其中还嵌入了一个可通过 WebSocket API 运行的聊天应用程序。如果用户正在积极地与某人聊天,则我们不应使HttpSession超时,因为这会带来非常糟糕的用户体验。但是,这正是JSR-356所做的。

另一个问题是,根据 JSR-356,如果HttpSession超时,则将使用该 HttpSession 创建的任何 WebSocket 超时,并且应强制关闭经过身份验证的用户。这意味着,如果我们正在应用程序中积极地聊天而不使用 HttpSession,那么我们也将断开与对话的连接!

WebSocket Usage

WebSocket Sample提供了有关如何将 Spring Session 与 WebSocket 集成的工作示例。您可以按照下面的基本集成步骤进行操作,但是在与自己的应用程序集成时,建议您遵循详细的 WebSocket 指南:

HttpSession Integration

使用 WebSocket 集成之前,应确保首先工作HttpSession Integration

Spring Configuration

在典型的 Spring WebSocket 应用程序中,用户将扩展AbstractWebSocketMessageBrokerConfigurer。例如,配置可能类似于以下内容:

@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

        public void registerStompEndpoints(StompEndpointRegistry registry) {
                registry.addEndpoint("/messages").withSockJS();
        }

        @Override
        public void configureMessageBroker(MessageBrokerRegistry registry) {
                registry.enableSimpleBroker("/queue/", "/topic/");
                registry.setApplicationDestinationPrefixes("/app");
        }
}

我们可以轻松地更新配置以使用 Spring Session 的 WebSocket 支持。例如:

src/main/java/samples/config/WebSocketConfig.java

@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig
                extends AbstractSessionWebSocketMessageBrokerConfigurer<ExpiringSession> { (1)

        protected void configureStompEndpoints(StompEndpointRegistry registry) { (2)
                registry.addEndpoint("/messages").withSockJS();
        }

        public void configureMessageBroker(MessageBrokerRegistry registry) {
                registry.enableSimpleBroker("/queue/", "/topic/");
                registry.setApplicationDestinationPrefixes("/app");
        }
}

要获得 Spring Session 支持,我们只需要更改两件事:

  • (1) 我们没有扩展AbstractWebSocketMessageBrokerConfigurer,而是扩展了AbstractSessionWebSocketMessageBrokerConfigurer
  • (2) 我们将registerStompEndpoints方法重命名为configureStompEndpoints

AbstractSessionWebSocketMessageBrokerConfigurer在幕后做什么?

  • WebSocketConnectHandlerDecoratorFactory作为WebSocketHandlerDecoratorFactory添加到WebSocketTransportRegistration。这样可以确保触发包含WebSocketSession的自定义SessionConnectEventWebSocketSession是终止 Spring Session 终止时仍打开的任何 WebSocket 连接所必需的。

  • SessionRepositoryMessageInterceptor作为HandshakeInterceptor添加到每个StompWebSocketEndpointRegistration中。这样可以确保将 Session 添加到 WebSocket 属性以启用更新上次访问时间。

  • SessionRepositoryMessageInterceptor作为ChannelInterceptor添加到我们的入站ChannelRegistration中。这样可以确保每次接收到入站消息时,Spring Session 的上次访问时间都会更新。

  • WebSocketRegistryListener被创建为 Spring Bean。这样可以确保我们将所有会话 IDMap 到相应的 WebSocket 连接。通过维护此 Map,我们可以在 Spring Session(HttpSession)终止时关闭所有 WebSocket 连接。

Spring 安全集成

Spring Session 提供与 Spring Security 的集成。

Spring Security 记住我支持

Spring Session 提供与Spring Security 的“记住我”身份验证的集成。支持将:

  • 更改会话过期时间

  • 确保会话 cookie 在Integer.MAX_VALUE过期。 cookie 过期设置为最大可能值,因为仅在创建会话时才设置 cookie。如果将其设置为与会话到期相同的值,则会话将在用户使用会话时进行更新,但 cookie 到期将不会更新,导致到期被固定。

要在 Java Configuration 中使用 Spring Security 配置 Spring Session,请使用以下指南:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        // ... additional configuration ...
        .rememberMe()
            .rememberMeServices(rememberMeServices());
}

@Bean
public SpringSessionRememberMeServices rememberMeServices() {
    SpringSessionRememberMeServices rememberMeServices =
            new SpringSessionRememberMeServices();
    // optionally customize
    rememberMeServices.setAlwaysRemember(true);
    return rememberMeServices;
}

基于 XML 的配置如下所示:

<security:http>
    <!-- ... -->
    <security:form-login />
    <security:remember-me services-ref="rememberMeServices"/>
</security:http>

<bean id="rememberMeServices"
    class="org.springframework.session.security.web.authentication.SpringSessionRememberMeServices"
    p:alwaysRemember="true"/>

Spring Security 并发会话控制

Spring Session 提供了与 Spring Security 的集成,以支持其并发会话控制。这允许限制单个用户可以同时具有的活动会话数,但是与默认的 Spring Security 支持不同,这也可以在集群环境中工作。这是通过提供 Spring Security 的SessionRegistry接口的自定义实现来完成的。

使用 Spring Security 的 Java 配置 DSL 时,您可以像这样通过SessionManagementConfigurer配置自定义SessionRegistry

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

        @Autowired
        FindByIndexNameSessionRepository<ExpiringSession> sessionRepository;

        @Override
        protected void configure(HttpSecurity http) throws Exception {
                http
                        // other config goes here...
                        .sessionManagement()
                                .maximumSessions(2)
                                .sessionRegistry(sessionRegistry());
        }

        @Bean
        SpringSessionBackedSessionRegistry sessionRegistry() {
                return new SpringSessionBackedSessionRegistry(this.sessionRepository);
        }
}

假设您还配置了 Spring Session 以提供一个FindByIndexNameSessionRepository返回ExpiringSession实例。

使用 XML 配置时,它看起来像这样:

<security:http>
    <!-- other config goes here... -->
    <security:session-management>
        <security:concurrency-control max-sessions="2" session-registry-ref="sessionRegistry"/>
    </security:session-management>
</security:http>

<bean id="sessionRegistry"
      class="org.springframework.session.security.SpringSessionBackedSessionRegistry">
    <constructor-arg ref="sessionRepository"/>
</bean>

假设您的 Spring Session SessionRegistry bean 名为sessionRegistry,这是所有SpringHttpSessionConfiguration子类使用的名称,但 MongoDB 的子类除外:那里称为mongoSessionRepository

Limitations

Spring Session 的 Spring Security 的SessionRegistry接口实现不支持getAllPrincipals方法,因为无法使用 Spring Session 检索此信息。 Spring Security 从不调用此方法,因此这只会影响自己访问SessionRegistry的应用程序。

API Documentation

您可以在线浏览完整的Javadoc。关键 API 如下所述:

Session

Session是名称值对的简化Map

典型用法如下所示:

public class RepositoryDemo<S extends Session> {
    private SessionRepository<S> repository; (1)

    public 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.getSession(toSave.getId()); (5)

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

    // ... setter methods ...
}
  • (1) 我们创建一个通用类型SSessionRepository实例,该实例扩展了Session。泛型类型在我们的类中定义。
  • (2) 我们使用SessionRepository创建一个新的Session并将其分配给S类型的变量。
  • (3) 我们与Session互动。在我们的示例中,我们演示了将User保存到Session
  • (4) 我们现在保存Session。这就是为什么我们需要通用类型S的原因。 SessionRepository仅允许保存使用相同SessionRepository创建或检索的Session实例。这允许SessionRepository进行特定于实现的优化(即仅写入已更改的属性)。
  • (5) 我们从SessionRepository检索Session
  • (6) 我们从Session获得了持久化的User,而无需显式转换属性。

ExpiringSession

ExpiringSession通过提供与Session实例的到期相关的属性来扩展Session。如果不需要与到期信息进行交互,则最好使用更简单的Session API。

典型用法如下所示:

public class ExpiringRepositoryDemo<S extends ExpiringSession> {
    private SessionRepository<S> repository; (1)

    public void demo() {
        S toSave = this.repository.createSession(); (2)
        // ...
        toSave.setMaxInactiveIntervalInSeconds(30); (3)

        this.repository.save(toSave); (4)

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

    // ... setter methods ...
}
  • (1) 我们创建一个通用类型SSessionRepository实例,该实例扩展了ExpiringSession。泛型类型在我们的类中定义。
  • (2) 我们使用SessionRepository创建一个新的ExpiringSession并将其分配给S类型的变量。
  • (3) 我们与ExpiringSession互动。在我们的示例中,我们演示了更新ExpiringSession可以在其失效之前处于非活动状态的时间。
  • (4) 我们现在保存ExpiringSession。这就是为什么我们需要通用类型S的原因。 SessionRepository仅允许保存使用相同SessionRepository创建或检索的ExpiringSession实例。这允许SessionRepository进行特定于实现的优化(即仅写入已更改的属性)。保存ExpiringSession后,上次访问的时间会自动更新。
  • (5) 我们从SessionRepository检索ExpiringSession。如果ExpiringSession过期,则结果为 null。

SessionRepository

SessionRepository负责创建,检索和保留Session实例。

如果可能,开发人员不应直接与SessionRepositorySession进行交互。相反,开发人员应该更喜欢通过HttpSessionWebSocket集成与SessionRepositorySession间接交互。

FindByIndexNameSessionRepository

Spring Session 使用Session的最基本 API 是SessionRepository。该 API 故意非常简单,因此很容易为其他实现提供基本功能。

一些SessionRepository实现可能也选择实现FindByIndexNameSessionRepository。例如,Spring 的 Redis 支持实现FindByIndexNameSessionRepository

FindByIndexNameSessionRepository添加了一种方法来查找特定用户的所有会话。通过确保使用用户名填充名称为FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME的会话属性来完成此操作。由于 Spring Session 不知道所使用的身份验证机制,因此开发人员有责任确保填充属性。下面显示了如何使用此示例:

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

Note

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

会话构建索引后,可以使用以下命令找到它:

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

EnableSpringHttpSession

可以将@EnableSpringHttpSession注解添加到@Configuration类,以将SessionRepositoryFilter作为名为“ springSessionRepositoryFilter”的 bean 公开。为了利用 Comments,必须提供单个SessionRepository bean。例如:

@EnableSpringHttpSession
@Configuration
public class SpringHttpSessionConfig {
        @Bean
        public MapSessionRepository sessionRepository() {
                return new MapSessionRepository();
        }
}

重要的是要注意,没有为您配置开箱即用的会话过期基础结构。这是因为会话期满之类的东西在很大程度上取决于实现。这意味着,如果您需要清理过期的会话,则您有责任清理过期的会话。

RedisOperationsSessionRepository

RedisOperationsSessionRepository是使用 Spring Data 的RedisOperations实现的SessionRepository。在 Web 环境中,通常与SessionRepositoryFilter结合使用。该实现支持SessionDestroyedEventSessionCreatedEventSessionMessageListener

实例化 RedisOperationsSessionRepository

下面是如何创建新实例的典型示例:

LettuceConnectionFactory factory = new LettuceConnectionFactory();
SessionRepository<? extends ExpiringSession> repository = new RedisOperationsSessionRepository(
        factory);

有关如何创建RedisConnectionFactory的其他信息,请参阅《 Spring Data Redis 参考》。

EnableRedisHttpSession

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

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

  • redisNamespace –允许为会话配置应用程序特定的名称空间。 Redis 键和通道 ID 的前缀为spring:session:<redisNamespace>:

  • redisFlushMode -允许指定何时将数据写入 Redis。仅当在SessionRepository上调用save时才是默认值。值RedisFlushMode.IMMEDIATE将尽快写入 Redis。

Custom RedisSerializer

您可以通过创建一个实现springSessionDefaultRedisSerializer的名为springSessionDefaultRedisSerializer的 Bean 来自定义序列化。

Redis TaskExecutor

订阅RedisOperationsSessionRepository以使用RedisMessageListenerContainer从 redis 接收事件。您可以通过创建名为springSessionRedisTaskExecutor和/或 Bean springSessionRedisSubscriptionExecutor的 Bean 来自定义这些事件的调度方式。有关配置 Redis 任务执行程序的更多详细信息,请参见here

Storage Details

以下各节概述了如何为每个操作更新 Redis。可以在下面找到创建新会话的示例。以下各节描述了详细信息。

HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \
	maxInactiveInterval 1800 \
	lastAccessedTime 1404360000000 \
	sessionAttr:attrName someAttrValue \
	sessionAttr2:attrName 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 中。使用 HMSET 命令设置和更新每个会话。每个会话如何存储的示例如下所示。

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

在此示例中,关于会话的以下语句是正确的:

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

  • 该会话的创建时间为格林尼治标准时间 1970 年 1 月 1 日午夜以来的 1404360000000.

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

  • 自格林尼治标准时间 1970 年 1 月 1 日午夜以来,该会话的最后访问时间为 1404360000000.

  • 会话具有两个属性。第一个是值为“ someAttrValue”的“ attrName”。第二个会话属性被命名为“ attrName2”,其值为“ someAttrValue2”。

Optimized Writes

RedisOperationsSessionRepositoryManagement 的Session实例会跟踪已更改的属性,并且只会更新这些属性。这意味着,如果一次写入一个属性并读取多次,我们只需要写入一次该属性。例如,假设先前的会话属性“ sessionAttr2”已更新。保存后将执行以下操作:

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

使用基于ExpiringSession.getMaxInactiveInterval()的 EXPIRE 命令将到期与每个会话相关联。例如:

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

您将注意到,所设置的到期时间是会话实际到期后的 5 分钟。这是必需的,以便在会话过期时可以访问该会话的值。会话本身实际上会过期五分钟后才在会话本身上设置一个过期,以确保将其清除,但是仅在我们执行了必要的处理之后。

Note

SessionRepository.getSession(String)方法可确保不会返回任何过期的会话。这意味着在使用会话之前无需检查到期时间。

Spring Session 依靠从 Redis 删除和过期keyspace notifications分别触发SessionDeletedEventSessionExpiredEventSessionDeletedEventSessionExpiredEvent确保与会话相关联的资源被清除。例如,当使用 Spring Session 的 WebSocket 支持时,Redis 过期或删除事件将触发与该会话关联的所有 WebSocket 连接被关闭。

不会直接在会话密钥本身上跟踪到期时间,因为这将意味着会话数据将不再可用。而是使用特殊的会话过期密钥。在我们的示例中,expires 键是:

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

然后,后台任务将使用这些 Map 来显式请求每个键。通过访问密钥而不是删除密钥,我们确保 Redis 仅在 TTL 过期时才为我们删除密钥。

Note

我们不会显式删除密钥,因为在某些情况下,可能会出现竞争情况,错误地将密钥标识为未过期。缺少使用分布式锁(这会损害我们的性能)的方法无法确保到期 Map 的一致性。通过简单地访问密钥,我们确保仅在该密钥上的 TTL 过期时才删除该密钥。

SessionDeletedEvent 和 SessionExpiredEvent

SessionDeletedEventSessionExpiredEvent都是SessionDestroyedEvent的类型。

RedisOperationsSessionRepository支持在删除Session或在SessionExpiredEvent过期时触发SessionDeletedEvent。这是确保正确清理与Session关联的资源所必需的。

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

可以通过监听Redis 键空间事件SessionMessageListener来触发SessionDeletedEventSessionExpiredEvent。为了使其正常工作,需要启用通用命令的 Redis 键空间事件和过期事件。例如:

redis-cli config set notify-keyspace-events Egx

如果您使用的是@EnableRedisHttpSessionSessionMessageListener并启用必要的 Redis Keyspace 事件,则会自动完成。但是,在安全的 Redis 环境中,禁用 config 命令。这意味着 Spring Session 无法为您配置 Redis Keyspace 事件。要禁用自动配置,请将ConfigureRedisAction.NO_OP作为 bean 添加。

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

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

XML 配置可以使用以下内容:

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

SessionCreatedEvent

创建会话后,将使用spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe通道将事件发送到 Redis,使得33fdd1b6-b496-4b33-9f7d-df96679d32fe是会话 ID。事件的主体将是创建的会话。

如果注册为 MessageListener(默认),则RedisOperationsSessionRepository会将 Redis 消息转换为SessionCreatedEvent

在 Redis 中查看会话

installing redis-cli之后,您可以检查 Redis 使用 redis-cli中的值。例如,在终端中 Importing 以下内容:

$ 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)
  • (1) 此项的后缀是 Spring Session 的会话标识符。
  • (2) 此项包含在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"

GemFireOperationsSessionRepository

GemFireOperationsSessionRepository是使用 Spring Data 的GemFireOperationsSessionRepository实现的SessionRepository。在 Web 环境中,通常与SessionRepositoryFilter结合使用。该实现支持SessionDestroyedEventSessionCreatedEventSessionMessageListener

通过 GemFire 使用索引

尽管有关对索引正确定义产生积极影响 GemFire 性能的最佳做法不在本文档的范围内,但重要的是要认识到 Spring Session Data GemFire 创建并使用索引来有效地查询和查找 Session。

开箱即用的 Spring Session 数据 GemFire 在主体名称上创建 1 个哈希类型的索引。查找主体名称的策略有两种不同的选择。第一种策略是将名称为FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME的 session 属性的值索引到相同的索引名称。例如:

String indexName = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
session.setAttribute(indexName, username);
Map<String, ExpiringSession> idToSessions = sessionRepository
        .findByIndexNameAndIndexValue(indexName, username);

通过 GemFire 和 Spring Security 使用索引

另外,Spring Session Data GemFire 会将 Spring Security 的当前Authentication#getName()Map 到索引FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME。例如,如果您使用的是 Spring Security,则可以使用以下命令查找当前用户的会话:

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String indexName = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
Map<String, ExpiringSession> idToSessions = sessionRepository
        .findByIndexNameAndIndexValue(indexName, authentication.getName());

通过 GemFire 使用自定义索引

这使开发人员能够以编程方式使用GemFireOperationsSessionRepository来有效地查询和查找具有给定主体名称的所有 Session。

此外,当开发人员识别出应由 GemFire 索引的 1 个或多个命名 Session 属性时,Spring Session Data GemFire 将在实现的 Session 的 Map-type attributes属性(即,在任意 Session 属性上)上创建基于范围的索引。

可以使用@EnableGemFireHttpSession注解上的indexableSessionAttributes属性指定要索引的会话属性。当他/她希望为由 GemFire 支持的 HttpSession 启用 Spring Session 支持时,开发人员将此 Comments 添加到其 Spring 应用程序@Configuration类中。

例如,以下配置:

@EnableGemFireHttpSession(indexableSessionAttributes = { "name1", "name2", "name3" })
public class GemFireHttpSessionConfig {
        // ...
}

将允许使用以下内容搜索会话:

String indexName = "name1";
session.setAttribute(indexName, attrValue);
Map<String, ExpiringSession> idToSessions = sessionRepository
        .findByIndexNameAndIndexValue(indexName, attrValue);

Note

只有在@EnableGemFireHttpSessionComments 的indexableSessionAttributes属性中标识的会话属性名称才定义索引。所有其他会话属性将不被索引。

但是,有一个警告。存储在可索引 Session 属性中的任何值都必须实现java.lang.Comparable<T>接口。如果这些对象值未实现Comparable,则当为具有持久性 Session 数据的 Region 定义了 Index,或者在运行时尝试为可索引 Session 属性分配的值不是Comparable时,GemFire 将在启动时引发错误。会话将保存到 GemFire。

Note

任何未构建索引的会话属性都可以存储非Comparable的值。

要了解有关 GemFire 的基于范围的索引的更多信息,请参阅在 Map 字段上创建索引

要总体上了解有关 GemFire 索引的更多信息,请参阅使用索引

MapSessionRepository

MapSessionRepository允许将ExpiringSession保留在Map中,其中键为ExpiringSession id,值为ExpiringSession。该实现可以与ConcurrentHashMap一起用作测试或便利机制。或者,它可以与分布式Map实现一起使用。例如,它可以与 Hazelcast 一起使用。

Instantiating MapSessionRepository

创建一个新实例非常简单:

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

使用 Spring Session 和 Hazlecast

Hazelcast Sample是一个完整的应用程序,演示了将 Spring Session 与 Hazelcast 一起使用。

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

./gradlew :samples:hazelcast:tomcatRun

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

它包括示例 Hazelcast MapListener实现,它们支持触发SessionCreatedEventSessionDeletedEventSessionExpiredEvent

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

./gradlew :samples:hazelcast-spring:tomcatRun

JdbcOperationsSessionRepository

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

实例化 JdbcOperationsSessionRepository

下面是如何创建新实例的典型示例:

JdbcTemplate jdbcTemplate = new JdbcTemplate();

// ... configure JdbcTemplate ...

PlatformTransactionManager transactionManager = new DataSourceTransactionManager();

// ... configure transactionManager ...

SessionRepository<? extends ExpiringSession> repository =
        new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager);

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

EnableJdbcHttpSession

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

  • tableName -Spring Session 用于存储会话的数据库表的名称

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

Custom LobHandler

您可以通过创建实现springSessionLobHandler的名为springSessionLobHandler的 Bean 来自定义 BLOB 处理。

Custom ConversionService

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

Storage Details

默认情况下,此实现使用SPRING_SESSIONSPRING_SESSION_ATTRIBUTES表存储会话。注意,表名可以很容易地自定义,如前所述。在这种情况下,将使用提供的表名(后缀_ATTRIBUTES)来命名用于存储属性的表。如果需要进一步的自定义,则可以使用set*Query setter 方法自定义存储库使用的 SQL 查询。在这种情况下,您需要手动配置sessionRepository bean。

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

例如,对于 PostgreSQL 数据库,您将使用以下模式脚本:

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

CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);

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

CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

并带有 MySQL 数据库:

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

CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (PRINCIPAL_NAME);

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

CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

Transaction management

JdbcOperationsSessionRepository中的所有 JDBC 操作都以事务方式执行。在传播设置为REQUIRES_NEW的情况下执行事务,以避免由于对现有事务的干扰而导致意外行为(例如,在已经参与只读事务的线程中执行save操作)。

HazelcastSessionRepository

HazelcastSessionRepositorySessionRepository实现,它将会话存储在 Hazelcast 的分布式IMap中。在 Web 环境中,通常与SessionRepositoryFilter结合使用。

实例化 HazelcastSessionRepositories

下面是如何创建新实例的典型示例:

Config config = new Config();

// ... configure Hazelcast ...

HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);

IMap<String, MapSession> sessions = hazelcastInstance
        .getMap("spring:session:sessions");

HazelcastSessionRepository repository =
        new HazelcastSessionRepository(sessions);

有关如何创建和配置 Hazelcast 实例的其他信息,请参阅Hazelcast documentation

EnableHazelcastHttpSession

如果您希望使用Hazelcast作为SessionRepository的后备源,则可以将@EnableHazelcastHttpSessionComments 添加到@Configuration类中。这扩展了@EnableSpringHttpSession注解提供的功能,但在 Hazelcast 中为您提供了SessionRepository。您必须提供一个HazelcastInstance bean 才能使配置生效。完整的配置示例可以在samples 和指南(从这里开始)中找到

Storage Details

会话将使用MapSessionRepository存储在 Hazelcast 中的分布式IMap中。 IMap接口方法将用于get()put()会话。此外,values()方法用于支持FindByIndexNameSessionRepository#findByIndexNameAndIndexValue操作,以及需要在 Hazelcast 中注册的适当的ValueExtractor。有关此配置的更多详细信息,请参考榛果 SpringsampleIMap中会话的到期由 Hazelcast 支持,用于设置将条目put()插入IMap时条目的生存时间。闲置时间超过生存时间的条目(会话)将自动从IMap中删除。

您无需在 Hazelcast 配置中为IMap配置任何设置,例如max-idle-secondstime-to-live-seconds

Basic Customization

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

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

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

Session Events

使用MapListener响应添加,删除和从分布式Map中删除的条目,这些事件将分别触发使用ApplicationEventPublisher来发布 SessionCreatedEvent,SessionExpiredEvent 和 SessionDeletedEvent 事件。

Spring Session 社区

我们很高兴将您视为我们社区的一部分。请在下面找到更多信息。

Support

您可以通过在带标签 spring-session 的 StackOverflow上提问来获得帮助。同样,我们鼓励通过回答有关 StackOverflow 的问题来帮助他人。

Source Code

我们的源代码可以在 github 上找到https://github.com/spring-projects/spring-session/

Issue Tracking

我们在https://github.com/spring-projects/spring-session/issues跟踪 github 问题中的问题

Contributing

我们感谢Pull Requests

License

Spring Session 是在Apache 2.0 许可证下发布的开源软件。

Community Extensions

NameLocation
Spring Session OrientDBhttps://github.com/maseev/spring-session-orientdb
Spring Session Infinispanhttp://infinispan.org/docs/dev/user_guide/user_guide.html#externalizing_session_using_spring_session

Minimum Requirements

Spring Session 的最低要求是:

  • Java 5+

  • 如果您在 Servlet 容器中运行(不是必需的),则 Servlet 2.5

  • 如果您正在使用其他 Spring 库(不需要),则最低要求的版本是 Spring 3.2.14. 在针对 Spring 3.2.x 重新运行所有单元测试时,我们建议尽可能使用最新的 Spring 4.x 版本。

  • @EnableRedisHttpSession需要 Redis 2.8. 这对于支持Session Expiration是必要的

Note

在 Spring Session 的核心部分,仅需要公共记录相关性。有关使用不带任何其他 Spring 依赖项的 Spring Session 的示例,请参阅hazelcast sample应用程序。