8、Spring Session-API文档

来源:互联网 发布:创意域名知乎 编辑:程序博客网 时间:2024/06/05 20:47

7、API文档

你可以通过浏览器阅读完整的 Javadoc在线文档,Spring Session关键API如下:

7.1. Session

Session是一个简单的键值对Map。

比较经典的使用如下:

public class RepositoryDemo<S extends Session> {    private SessionRepository<S> repository;     public void demo() {        S toSave = this.repository.createSession();         User rwinch = new User("rwinch");        toSave.setAttribute(ATTR_USER, rwinch);        this.repository.save(toSave);         S session = this.repository.findById(toSave.getId());         User user = session.getAttribute(ATTR_USER);        assertThat(user).isEqualTo(rwinch);    }    // ... setter methods ...}
  • 我们创建一个泛型SessionRepository实例,S继承Session。泛型类型在我们的类中定义。
  • 使用SessionRepository 创建一个新的Session并指定为S类型的变量。
  • 与Session进行交互。在我们的例子中,我么演示了保存一个User到Session中。
  • 我们现在要保存Session。为什么我们需要泛型S,因为SessionRepository 仅仅允许保存由相同SessionRepository创建或者获取的Session实例,使用泛型S允许我们对SessionRepository 做一些特殊的优化实现(比如:仅仅保存写属性)。
  • 从SessionRepository提取Session
  • 从Session中获取持久化的User,不需要显示的转换。

Session API也提供了Session实例过期属性。

比较常用的如下:

public class ExpiringRepositoryDemo<S extends Session> {    private SessionRepository<S> repository;     public void demo() {        S toSave = this.repository.createSession();         // ...        toSave.setMaxInactiveInterval(Duration.ofSeconds(30));         this.repository.save(toSave);         S session = this.repository.findById(toSave.getId());         // ...    }    // ... setter methods ...}
  • 我们创建一个泛型SessionRepository实例,S继承Session。泛型类型在我们的类中定义。
  • 使用SessionRepository 创建一个新的Session并指定为S类型的变量。
  • 与Session进行交互。在我们的实例中,我们演示的是Session过期之前不被激活的时间量。
  • 保存Session,为什么我们需要泛型S,因为SessionRepository 仅仅允许保存由相同SessionRepository创建或者获取的Session实例,使用泛型S允许我们对SessionRepository 做一些特殊的优化实现(比如:仅仅保存写属性)。在Session保存时,最后一次访问时间将会自动更新。
  • 从SessionRepository中获取Session,如果Session过期,将会返回null。

7.2. SessionRepository

SessionRepository负责创建、获取、持久化Session实例。

如果可能,开发人员不应该直接与SessionRepository或Session进行交互。相反,开发者更应该倾向于通过HttpSession个Websocket的集成来间接的与SessionRepository和Session进行交互。

7.3. FindByIndexNameSessionRepository

Spring Session使用Session最基本的API是SessionRepository。这个API非常的简单,因此通过基本的功能可以提供额外的实现。

一些SessionRepository 的实现也可以选择实现FindByIndexNameSessionRepository,比如,Spring Redis的支持实现了FindByIndexNameSessionRepository。

FindByIndexNameSessionRepository添加一个单独的方法为指定用户查询所有会话。这是通过设置名为FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME的Session的属性值为指定用户的username来完成的。开发人员有责任确保属性被赋值,因为Spring Session不会在意被使用的认证机制。一个使用实例如下:

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

FindByIndexNameSessionRepository的一些实现会提供一些hooks(钩子)自动的索引其他的session属性。比如,很多实现都会自动的确保当前的Spring Security用户名称可通过索引名称FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME进行索引。

一旦会话被索引,就可以通过下面的代码检索:

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

7.4. EnableSpringHttpSession

@EnableSpringHttpSession注解可以被添加到一个@Configuration注解的类暴露SessionRepositoryFilter作为一个名为springSessionRepositoryFilter的Bean,为了使用这个注解,一个单独的SessionRepository Bean必须被提供。如:

@EnableSpringHttpSession@Configurationpublic class SpringHttpSessionConfig {    @Bean    public MapSessionRepository sessionRepository() {        return new MapSessionRepository(new ConcurrentHashMap<>());    }}

需要注意的是,对于会话过期一些开箱即用的基础架构并没有被配置。这是因为像会话过期的这些内容拥有很高的实现依赖。这就意味着如果你需要清空已经过期的会话,你就有责任清理这些已过期的会话。

7.5. RedisOperationsSessionRepository

如何创建新实例的经典例子如下:

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

更多关于如何创建RedisConnectionFactory的信息,可以参考Spring Data Redis参考文档。

7.5.2. EnableRedisHttpSession

在web环境中,创建新的RedisOperationsSessionRepository实例最简单的方法就是使用@EnableRedisHttpSession注解。完整的例子请参考Samples and Guides (Start Here) ,你可以使用下面的这些属性个性化你的配置:

  • maxInactiveIntervalInSeconds :会话过期之前的时间总和(秒)
  • redisNamespace :允许为会话配置应用指定的名称空间,Redis keys和channel ids将会以spring:session:<redisNamespace>:作为前缀。
  • redisFlushMode :在数据写入Redis时指定。默认在SessionRepository的save方法被调用的时候。RedisFlushMode.IMMEDIATE的值将会被尽可能的写入。

自定义RedisSerializer

你可以通过创建一个名为springSessionDefaultRedisSerializer且实现了RedisSerializer<Object>的bean个性化序列化。

7.5.3. Redis TaskExecutor

RedisOperationsSessionRepository被订阅使用RedisMessageListenerContainer从Redis接收事件。你可以通过创建一个名为springSessionRedisTaskExecutor和/或springSessionRedisSubscriptionExecutor的Bean个性化事件的发送方式,配置redis事务执行的更多详细信息可参考这里

7.5.4. 存储详情

本节以下的部分主要描述对于每一次操作Redis如何更新。创建一个新的会话例子如下。接下来的章节会介绍这些详细信息。

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

保存session

每个session都会以Hash的形式保存在Redis中。每一个session通过使用HMSET设置和更新。每个session存储的例子如下:

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

在这个例子中,session的实际描述如下:

  1. session id为33fdd1b6-b496-4b33-9f7d-df96679d32fe
  2. session的创建时间是从1970年1月1号午夜到现在的1404360000000毫秒
  3. session过期时间为1800秒(30分钟)
  4. session最后一次访问时间是从1970年1月1号午夜到现在的1404360000000毫秒
  5. session有两个属性,第一个属性是attrName,值为someAttrValue,第二个属性为attrName2,值为someAttrValue2

Optimized Writes

由RedisOperationsSessionRepository 管理的Session实例跟踪变化的属性,而这些属性只能更新。这就意味着一个属性被写入一次且被读很多次,而我们仅仅只需要写入一次。比如,假设很早以前的session属性sessionAttr2被更新过,保存之后将会执行下面的操作:

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

会话过期

使用基于Session.getMaxInactiveInterval()的EXPIRE命令可以关联每一个会话和它的过期信息。如:

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

需要注意的是到期被设置为会话实际到期之后的5分钟,这是必要的,因为这样可以在会话到期之后还可以访问会话的值。会话到期被设置成会话实际到期之后的5分钟确保会话可以被清除,但是仅仅是在我们执行完成所有必要的处理之后。

SessionRepository.findById(String)方法确保能够返回未过期的会话。这就意味着使用会话之前我们没有必要去检查过期。

Spring Session依赖Redis的delete和expired keyspace notification,分别触发SessionDeletedEvent 和SessionExpiredEvent。SessionDeletedEvent或SessionExpiredEvent可以确保为会话分配的资源可以被清理。比如,当使用Spring Session 的WebSocket支持Redis过期或者删除事件被触发时,所有被分配给这些websocket链接的session都会被关闭。

使用session key本身不能直接跟踪到过期时间,对于跟踪过期时间意味着此session的数据可能不在适用。而是使用特殊的expires key,在我们的例子中expires key是:

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

当session的expires key被删除或者过期时,keyspace notification会触发实际session的查询和SessionDestroyedEvent的触发。

只依赖Redis过期时间有一个问题就是如果key没有被访问,那么Redis就不能保证何时触发过期事件。具体来说,Redis用于清理 expired keys的后台事务是低优先级的事务且可能不会触发key的到期。更多详细内容可以参考Redis文档的Timing of expired events。

为了规避过期事件不能保证被触发的实时,当期望key到期时我们要确保每一个key都被访问过。这就意味着如果在key上的TTL过期了,当尝试访问可以的时候Redis将会移除key且触发过期事件。

为此,每个会话的到期也能够跟踪到最近的几分钟,允许后台事务访问潜在的已过期会话,确保Redis的过期事件能够被执行。如:

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

后台事务会使用映射直接去请求每一个key,通过访问key,然后删除,我们确保如果TTL已经过期,Redis就会为我们删除的key。

我们没有直接删除keys是因为在某些情况下,可能导致一种错误的将未过期的Key识别为已过期的竞争条件。没有使用分布式锁(这将会损失我们的性能)就没有办法确保到期时间的一致性。简单的访问key,如果在key上的TTL已经过期,我们仅能确保key被移除。

7.5.5 SessionDeletedEvent 和 SessionExpiredEvent

SessionDeletedEvent 和 SessionExpiredEvent都是SessionDestroyedEvent的两种类型。

RedisOperationsSessionRepository支持无论何时Session被删除之后会触发SessionDeletedEvent或者当Session过期时会触发SessionExpiredEvent。这样做是很有必要的,因为这样可以确保分配给Session的资源可以被清理。

比如说,当整合WebSocket时SessionExpiredEvent负责关闭所有激活的连接。

触发SessionDeletedEvent 和 SessionExpiredEvent让它们变得可用可通过监听Redis Keyspace事件的SessionMessageListener。为了让它工作,那么用于通用命令的Redis Keyspace事件和Expired事件都需要被启用。如:

redis-cli config set notify-keyspace-events Egx

如果你使用了@EnableRedisHttpSession注解了SessionMessageListener且自动的完成了必要的Redis Keyspace事件的启动。然而,在安全的Redis环境中配置命令是被禁止的。这就意味着Spring Session不能为你配置Redis Keyspace事件。要禁用自动配置添加ConfigureRedisAction.NO_OP作为一个Bean。

例如,Java配置如下:

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

XML配置如下:

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

7.5.6. SessionCreatedEvent

当一个session被创建,一个事件借助于spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe通道将会发送给Redis,33fdd1b6-b496-4b33-9f7d-df96679d32fe是session id。事件的body将会成功被创建的session。

如果注册作为MessageListener(默认),RedisOperationsSessionRepository将会转化Redis消息为SessionCreatedEvent。

7.5.7. 在Redis中查看Session

安装Redis客户端之后,你可以在Redis客户端中检查这些值,如在终端输入如下内容:

$ redis-cliredis 127.0.0.1:6379> keys *1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" 2) "spring:session:expirations:1418772300000" 
  1. 这个key的后缀是Spring Session的唯一标识
  2. 这个key包含所有的session ids,这些session ids在1418772300000时间点会被删除。

你也可以查看每个session的值:

redis 127.0.0.1:6379> hkeys spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb0211) "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"

7.6. MapSessionRepository

MapSessionRepository允许持久化Session到一个Map当中,Map的key为Session id,Map的值为Session。实现可以和ConcurrentHashMap一起用作测试或者便利机制。另外,他也可以和分布式Map一起使用。比如,他可以和Hazelcast一起使用。

7.6.1. 初始化MapSessionRepository

创建一个新的实例:

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

7.6.2. 使用Spring Session和Hazlecast

Hazelcast Sample(https://github.com/spring-projects/spring-session/tree/2.0.0.M4/samples/javaconfig/hazelcast)是一个完整的用于演示Spring Session和Hazlecast的应用。

通过如下命令就可以运行:

./gradlew :samples:hazelcast:tomcatRun

Hazelcast Sample是一个完整的用于演示Spring Session和Hazlecast以及Spring Security的应用。

它包括支持触发SessionCreatedEvent、SessionDeletedEvent和SessionExpiredEvent的Hazelcast MapListener实现的例子。

使用下面的命令就可以运行:

./gradlew :samples:hazelcast-spring:tomcatRun

7.7. JdbcOperationsSessionRepository

JdbcOperationsSessionRepository是一个SessionRepository 的实现,它使用Spring的JdbcOperations将会话存储到关系型数据库中。在web环境中,最典型的就是结合SessionRepositoryFilter一起使用。请注意,这个实现不支持session事件的发布。

7.7.1. 初始化JdbcOperationsSessionRepository

如何创建实例的经典样例如下:

JdbcTemplate jdbcTemplate = new JdbcTemplate();// ... configure JdbcTemplate ...PlatformTransactionManager transactionManager = new DataSourceTransactionManager();// ... configure transactionManager ...SessionRepository<? extends Session> repository = new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager);

关于如何创建和配置JdbcTemplate 和PlatformTransactionManager的内容,请参考Spring Framework参考文档。

7.7.2. EnableJdbcHttpSession

在web环境中,创建一个新的JdbcOperationsSessionRepository 对象最简单的方法就是使用@EnableJdbcHttpSession,完整的样例使用可以在HttpSession with JDBC查看。你可以使用下面的这些属性个性化你的配置:

  1. tableName :使用Spring Session存储会话的数据库表名。
  2. maxInactiveIntervalInSeconds :session到期之前的所有时间总数(秒)。

自定义LobHandlernts

你可以通过创建一个名为springSessionLobHandler的Bean个性化BLOB 的处理。

自定义ConversionService

你可以通过提供一个ConversionService实例个性化session默认的序列化和发序列化。当在典型的Spring环境中工作时,默认的ConversionService Bean(名为conversionService)会被自动抽取出来用于序列化和反序列化。然而,你可以提供一个名为springSessionConversionService的Bean来重写默认的ConversionService。

7.7.3. 存储细节

默认情况下,使用SPRING_SESSION 和SPRING_SESSION_ATTRIBUTES 表实现session的存储。需要注意的是我们已经讲过这个表名是可以个性化的,在这种情况下,存储属性的表名要使用提供的表名加上_ATTRIBUTES后缀。如果还需要更深入的个性化,repository所使用的SQL查询也可以通过set*Query设置器方法个性化,在这种情况下,你就需要手动配置sessionRepository Bean。

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

例如,PostgreSQL数据库你就可以使用下面的脚本:

CREATE TABLE SPRING_SESSION (    PRIMARY_ID CHAR(36) NOT NULL,    SESSION_ID CHAR(36) NOT NULL,    CREATION_TIME BIGINT NOT NULL,    LAST_ACCESS_TIME BIGINT NOT NULL,    MAX_INACTIVE_INTERVAL INT NOT NULL,    EXPIRY_TIME BIGINT NOT NULL,    PRINCIPAL_NAME VARCHAR(100),    CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID));CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);CREATE TABLE SPRING_SESSION_ATTRIBUTES (    SESSION_PRIMARY_ID CHAR(36) NOT NULL,    ATTRIBUTE_NAME VARCHAR(200) NOT NULL,    ATTRIBUTE_BYTES BYTEA NOT NULL,    CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),    CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE);CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID);

MySQL数据库就是用下面的脚本:

CREATE TABLE SPRING_SESSION (    PRIMARY_ID CHAR(36) NOT NULL,    SESSION_ID CHAR(36) NOT NULL,    CREATION_TIME BIGINT NOT NULL,    LAST_ACCESS_TIME BIGINT NOT NULL,    MAX_INACTIVE_INTERVAL INT NOT NULL,    EXPIRY_TIME BIGINT NOT NULL,    PRINCIPAL_NAME VARCHAR(100),    CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)) ENGINE=InnoDB;CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);CREATE TABLE SPRING_SESSION_ATTRIBUTES (    SESSION_PRIMARY_ID CHAR(36) NOT NULL,    ATTRIBUTE_NAME VARCHAR(200) NOT NULL,    ATTRIBUTE_BYTES BLOB NOT NULL,    CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),    CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE) ENGINE=InnoDB;CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID);

7.7.4. 事务管理

JdbcOperationsSessionRepository 中的所有JDBC操作都是以事务的方式执行。通过广播设置REQUIRES_NEW来执行事务,主要是为了避免由于对现有事务的干扰而导致意外的行为(比如:在一个已经执行只读事务的线程中执行save操作)。

7.8. HazelcastSessionRepository

HazelcastSessionRepository 是SessionRepository 的一个实现,主要用于在Hazelcast的分布式IMap中存储会话。在web环境中,最典型的就是结合SessionRepositoryFilter一起使用。

7.8.1. 初始化HazelcastSessionRepository

创建新实例的例子如下:

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 参考文档。

7.8.2. EnableHazelcastHttpSession

如果你想使用Hazelcast为SessionRepository做备份资源,可以将@EnableHazelcastHttpSession注解添加到一个@Configuration注解的类中。他就会继承由@EnableSpringHttpSession注解所提供的功能,但是在Hazelcast中让SessionRepository为你提供服务,你必须要提供一个单一的HazelcastInstance Bean才能让配置工作。

完整的配置样例可参考HttpSession with Hazelcast。

7.8.3. 基本个性化

你可以在@EnableHazelcastHttpSession注解中使用一下的属性个性化配置

  1. maxInactiveIntervalInSeconds :session过期前的时间总和(秒),默认1800秒(30分钟)。
  2. sessionMapName :分布式Map的名称,此Map在Hazelcast中将会被用于存储session数据。

7.8.4. Session 事件

使用MapListener响应实例从分布式Map中添加、退出、移除,这些事件将分贝触发使用ApplicationEventPublisher发布SessionCreatedEvent, SessionExpiredEvent和SessionDeletedEvent事件。

7.8.5. 存储详情

在Hazelcast中会话将会被存储在分布式IMap中。IMap接口的get()和put()方法将会被会话所使用。此外,values()方法可以被用来支持FindByIndexNameSessionRepository#findByIndexNameAndIndexValue操作,以及需要使用Hazelcast注册的ValueExtractor ,需要了解更多请参考HttpSession with Hazelcast。在IMap中session的过期时间是由Hazelcast支持处理,他会在实体通过put()方法put到IMap中时设置一个存活时间来实现。如果实例(session)闲置的时间超过存活时间,那么它将会自动的从IMap中移除。

你不需要在Hazelcast的配置文件中为IMap进行如max-idle-seconds 或 time-to-live-seconds的设置。

记住如果你使用Hazelcast的MapStore持久化你的会话,当从MapStore重新加载会话时IMap 有一些局限性。

  1. 重新加载触发EntryAddedListener,在SessionCreatedEvent 的结果就会被重新发布。
  2. 重新加载为给定的IMap使用默认的TTL,会话中的结果将会丢失原始的TTL。

如果有翻译不对的地方,请大家在评论区拍砖

原文:https://docs.spring.io/spring-session/docs/2.0.0.M4/reference/html5/#api

原创粉丝点击