spring session原理

首先非常感谢学海划舟,本文部分总结来自于他的博文,以下为链接:
http://blog.csdn.net/zcl111/article/details/51700925
spring session是干什么的,有什么作用?这些读者可以自行从百度上找到,本文主要是从配置、原理、流程等几个方面来了解spring session,如果有什么不对的地方,望各位看官指正。好了,话不多说,来正餐:
一.配置
1.添加依赖
这里写图片描述
这里写图片描述

2.添加配置,这里我们用的是注解方式
因为是redis,所以需要启用RedisHttpSession,在这里需要添加@EnableRedisHttpSession、@Configuration
这里写图片描述
在此配置里面,需要配置JedisConnectionFactory,主要是供spring session和redis服务端的交互。
这里写图片描述
因为redis服务端用的是集群,所以选用RedisClusterConfiguration集群配置,同时添加池配置。
其他的可以选配CookieSerializer,HttpSessionListener
这里写图片描述
这里写图片描述
CookieSerializer主要是cookie的相关配置,例如名称,加密等当然还可以自定义key、value的序列化方式,然后将其塞入RedisTemplate即可。
这里写图片描述
如果上述选配不配置的话,是有默认配置的,相关配置是通过@EnableRedisHttpSession引入的,此注解拥有自己的一些配置,如maxInactiveIntervalInSeconds,redisNamespace,redisFlushMode。同时它还引入了RedisHttpSessionConfiguration配置类。
这里写图片描述
在RedisHttpSessionConfiguration里面加载了很多的bean,如RedisMessageListenerContainer
,RedisTemplate,RedisOperationsSessionRepository,当然还有最重要的SessionRepositoryFilter,后面DelegatingFilterProxy获取的filter就是在这个时候加载的。
同时由于默认的SessionRepositoryFilter的加载优先级最高,但是需求上又需要自定义SessionRepositoryFilter的加载顺序,所以放弃了通过继承AbstractHttpSessionApplicationInitializer来用于向应用容器添加springSessionRepositoryFilter的方式,转而采用web.xml方式。
这里写图片描述
这样可以很轻易的控制filter的加载顺序。

二、运行流程
1.因为我们在web.xml里配置了代理过滤器:DelegatingFilterProxy,所以当请求进来后,进入DelegatingFilterProxy过滤器doFilter(**):
这里写图片描述
在方法中获取spring容器里的springSessionRepositoryFilter并初始化,再调用
springSessionRepositoryFilter的父类OncePerRequestFilter的doFilter方法,在此方法中调用doFilterInternal进入到OncePerRequestFilter的doFilterInternal方法,此方法被springSessionRepositoryFilter过滤器重写了,所以最后调用的是springSessionRepositoryFilter的doFilterInternal方法,spring session的核心功能是基于此过滤器实现的。

2.在springSessionRepositoryFilter过滤器doFilterInternal(**):
这里写图片描述
其中:
这里写图片描述
就是对httpServletRequest进行包装,重载了httpServletRequest中关于session操作的方法。我们可以看到SessionRepositoryRequestWrapper中一些重载方法:getSession,isRequestedSessionIdValid。
需要注意的是它并不是直接对session进行操作,而是通过HttpSessionWrapper的封装间接操作Session的。
这里写图片描述
进入到父类ExpiringSessionHttpSession
这里写图片描述
我们可以看到其中包含了session属性,所以对httpSession的操作都变成HttpSessionWrapper对Session属性的操作。

2.spring session的持久化
当我们调用HttpSession的setAttribute(String name, Object value);方法时。调用追踪的时候就会看到:
这里写图片描述
因为我们用的是redis,所以调用的是RedisOperationsSessionRepository的子类RedisSession
请注意:RedisSession在实例化的时候会初始化id,delta等,delta属性包含创建时间点、持续时间、最后一次访问时间点等。
这里写图片描述
回到setAttribute方法:
这里写图片描述
先将key|value塞到mapSession里去,然后调用如下方法:
这里写图片描述
主要操作是将key|value放入delta,再根据实例化时redisFlushMode的属性,判断是否立即持久化。默认的是RedisFlushMode.ON_SAVE。即在web环境时,response在commit时。
这里写图片描述

3.关于response在何时commit,在SessionRepositoryFilter的doFilterInternal中可以看到:
这里写图片描述
在实际跟踪代码发现是当业务代码执行完后,再执行此finally代码块,即commitSession。
持久化,同时会把session唯一标识放入web中(cookie策略是放入cookie中,header策略是放入header中等)。
这里写图片描述
在这里我们最终看到了session的持久化方法save
这里写图片描述
先看saveDelta方法
这里写图片描述
从源码注释我们可以看到,此方法主要是保存任意变化的属性以及更新当前session的过期时间。
Save方法的余下代码主要是当第一次保存session时,调用RedisTemplate的convertAndSend方法来序列化key、vlaue,同时将其保存到redis中。
回到commitSession方法:
这里写图片描述
当发现在request里没有sessionID或sessionID对应的session不存在或session存在但是和当前session不一致时,都会触发调用CookieHttpSessionStrategy的onNewSession方法
这里写图片描述
此方法就是将当前session的一些信息,比如sessionId,别名信息封装为一个CookieValue实例,同时也会将我们在配置里配置cookie的一些信息如Path,Secure,domainName等信息赋值到此实例中,然后将此实例写到响应里面返回给浏览器。
CookieSerializer的配置如下图所示:
这里写图片描述
另外,查阅相关资料发现,在spring session持久化session到redis时有如下操作:
这里写图片描述
首先保存session到redis中,由于是hash类型,使用HMSET命令
这里写图片描述
其中包含创建时间点、持续时间、最后一次访问时间点以及2个键值对属性。
当属性变更时:
这里写图片描述
session的过期通过EXPIRE 命令控制:
这里写图片描述
注意到这里是2100而不是1800,说明设置的真实过期是在过期5分钟后。
至于为什么要这样设置,主要是涉及到redis对session的过期处理方式。
为了保证在session销毁时,把session关联的资源都清理掉,需要redis在session过期时,通过“keyspace notifications ”触发SessionDeletedEvent/SessionExpiredEvent 事件。因为session事件中涉及session信息,所以要保证这个时候,session的相关信息还是要存在。所以要把session的真实过期事 件设置比需要的还有长5分钟,这样才能保证逻辑过期后,依然能获取到sssion信息。
因为要触发事件,所以session得过期时间设置的比逻辑上的晚5分钟,但是这样会造成不符合我们的逻辑设定,为此,过期的设置添加一些额外处理:
这里写图片描述
这样,当spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 在我们逻辑设定的时间点过期时,就会触发session销毁事件,但是因为session的信息延后5分钟过期,又保证了在session事件中能正常获 取到session信息。
但是有个问题出现,这是redis独有的问题:redis的过期处理,实质上就是先看下这个key的过期时间,如果过期了,才删除。所以,当key 没有被访问到时,我们就不能保证这个实际上已经过期的session在何时会触发session过期事件。尤其redis后台清理过期的任务的优先级比较 低,很可能不会触发的。
为了规避这个问题。我们可以确保:当每个key预期到期时,key可以被访问到。这个就意味着:当key的生命周期到期时,我们试图通过先访问到它,然后来让redis删除key,同时触发过期事件。为此,要让每个session在过期时间点附近就可以被访问追踪到,这样就可以让后台任务访问到可能过期的session,以更确定性的方式确保redis可以触发过期事件:
这里写图片描述
后台任务通过映射(eg:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe)关联到准确的请求key。通过访问key,而不是删除它,我们可以确保只有当生命周期过期时,redis才会删除这个key。我们并不能很确切地删除这些key,因为在一些情况下,可能会错误地认定key过期了,除了使用分布式锁外,没有任何一种方法能保证过期映射的一致性。但是通过简单的访问方式,我们却可以保证只有过期能才会被删除。

4.session监听:为了能够正常作用,需要打开redis的设置,redis默认是关闭的,其实就是redis的键空间通知,可以通过命令和修改配置文件redis.conf来开启此功能。
这里写图片描述
这里写图片描述
Session的创建流程见1-3,在最后的RedisTemplate的convertAndSend(String channel, Object message)方法里面
这里写图片描述
在保存的回调方法里面做了消息发布。
而session的其他操作如过期、销毁等会触发RedisOperationsSessionRepository的onMessage方法,在这里判断事件的具体语义,再分别调用对应的处理逻辑来发布session事件:
这里写图片描述

三、流程图
这里写图片描述

尾巴:乐于分享,共同成长!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值