Spring Session

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

Introduction

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

1.3 的新功能

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

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

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

表 1.示例应用程序

Source Description Guide
HttpSession 演示如何使用 Spring Session 用 Redis 存储替换HttpSession HttpSession Guide
HttpSession XML 演示如何使用 Spring Session 通过基于 XML 的配置用 Redis 存储替换HttpSession HttpSession 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 替换为HttpSession HttpSession GemFire P2P 指南
使用 XML 的 HttpSession 与 GemFire(P2P) 演示如何使用 Spring Session 通过配置有 XML 的 P2P 拓扑将 G_Fire 替换为HttpSession HttpSession 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的透明集成,但是我们可以从中获得什么好处呢?

带有 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)
        }
}
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

基于 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"/>
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;
                        }
                };
        }

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 的某些方面:

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

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"/>

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"/>

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 *。

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

Tip

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

Note

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

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

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

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"/>

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

}

有关如何配置与数据访问相关的问题的更多信息,请参阅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

基于 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>

有关如何配置与数据访问相关的问题的更多信息,请参阅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 {
}
配置数据源

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

会话序列化机制

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

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

}

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)

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

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

Such that:

自动会话别名包含 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)
        }
}

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。要使用此支持,您需要:

如果您使用的是带有 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 支持,我们只需要更改两件事:

AbstractSessionWebSocketMessageBrokerConfigurer在幕后做什么?

Spring 安全集成

Spring Session 提供与 Spring Security 的集成。

Spring Security 记住我支持

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

要在 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 ...
}

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 ...
}

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 和指南(从这里开始)中找到。您可以使用以下属性来自定义配置:

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

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

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)

您还可以查看每个会话的属性。

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 和指南(从这里开始)中找到。您可以使用以下属性来自定义配置:

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上使用以下属性来自定义配置:

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

Name Location
Spring Session OrientDB https://github.com/maseev/spring-session-orientdb
Spring Session Infinispan http://infinispan.org/docs/dev/user_guide/user_guide.html#externalizing_session_using_spring_session

Minimum Requirements

Spring Session 的最低要求是:

Note

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

首页