Spring-Session 基础知识点 和 源码分析(下)

一、Spring-Session的使用场景:

 

场景一 、相同域名下相同项目实现session共享。

  1. 集群部署之后我们需要使用spring-session。

原因: 我们tomcat 服务器集群部署(处理动态资源)和nginx集群部署(处理静态资源)之后,配合我们的 主nginx负载均衡。我们的主nginx 根据 服务器的压力或者某种规则来进行分发请求。一个用户在一次会话当中,发出很多次请求,这些请求可能被分发到了 不同的 tomcat服务器进行处理,所以这时候需要 我们 spring-session 框架 将我们的session对象实现共享。

我们 9100 和 9200 部署 p2p项目模块,在9300中部署dataservice项目模块。

Nginx.conf 中配置的负载均衡:

我们的 127.0.0.1 是 windows 和 linux 通用的 地址,我们的 windows可以使用它,我们的linux也可以使用它,这个127.0.0.1 都代表本机的意思,代表windows本机或者linux本机。我们也可以在上面的配置中直接写linux的地址:192.168.242.128 。

 

知识点补充:nginx重启的命令。

 

  1. 向p2p项目中 添加 spring-Session框架所以来的jar包。不需要修改我们的pom文件,直接将jar导入到复制粘贴到 lib包中就可以了,因为我们的项目已经上线了,pom文件中所有的jar都下载完成了。
  2. 向9100和9200端口的tomcat中的p2p项目的web.xml中添加spring-session全局过滤器。
  3. 向配置文件包中,添加 appliction-session配置文件,还有redis.properties配置文件。
  4. 不需要使用监听器listener来加载我们的spring容器,因为我们的springMVC已经将spring容器加载了。 就是在 配置中央调度器的时候加载了我们的 spring总配置文件从而加载了 spring容器了。

 

上面的步骤就实现了:

配置多个tomcat(集群),然后使用nginx负载均衡。然后使用spring-session来实现我们的 session共享。因为是将我们的session对象存放进我们的redis中,这个redis是一个公共的部分,所以所有的tomcat都可以有需要,自己去redis中获取,但是我们存放的session最大空闲时间为 30 分钟。

 

 

场景二、同域名下不同项目实现session共享。 实质上和相同项目是一样的。

假如 域名为 www.myweb.com,项目都为 p2p,那么生成的session的sessionid存入cookie之后,这个cookie存放的位置是: myweb/com/www/ p2p/cookie对象   

如果 两个不同的项目,一个是 pay一个是shop,我们第一次访问pay,生成的session将sessionId存放进cookie中,然后这个cookie被存放在  myweb/com/www/pay/下面。

然后同一个用户再发出一次请求访问,但是我们的这次的请求是请求 访问我们的shop项目,所以浏览器会从   myweb/com/www/shop/下面获取 cookie ,但是不会从 myweb/com/www/pay/下面获取cookie。

  Domain + Path = cookie的存放路径。

意思是这样的:

  1. Localhost:9200/pay/setsession 这个请求的时候,我们的浏览器是从 myweb/com/www/pay/这个包结构下面获取cookie中的sessionId。
  2. Localhost:9200/shop/getsession 这个请求的时候,我们的浏览器是从Localhost:9200/shop/ 这个包结构下面获取cookie中的sessionId。
  3. 存放在不同包结构下面的cookie,cookie保存的sessionId也肯定不一样,从而导致从redis获取的session也肯定不一样。从而没有达到session共享的效果。
  4. 所以我们 需要让 每次发出请求后,返回的sessionid存放在 相同包结构下的cookie中,如:我们的cookie都放在根目录下,也就是myweb/com/www/下面,这样在一次会话中,发出所有的请求 浏览器都会去相同的包结构中获取cookie中的sessionId,这样就保证session的共享了。

 

这种情况造成的原因是:因为我们的项目不同,项目不同所以项目名不同,项目名不同从而导致了 tomcat的上下文根 不同,从而导致cookie的存放位置不同。存放位置不同所以cookie就不同,里面存放的sessionI就不同,从而session也不同,所以服务端就认为这是两个不同的请求。

 

********怎么设置我们cookie的存放位置呢?

我们的 cookie的存放位置 与Domain 和 Path有关。

在 spring-session 核心类 中 添加 property 属性。

这个核心类中有一个属性:

意思是 cookie的序列化方式,这个属性就是用来设置cookie的存放方式的,注意了:

核心类中的这个 cookie序列化属性 只是用来 设置 cookie的存放的方式的。

这个类是用来设置 cookie的默认序列化方式,也就是专门用来设置cookie默认存放路径的,注意了 是用来设置 cookie存放路径的。

核心类的属性和DefaultCookieSerializer这个类的属性配合起来就可以 设置我们的 cookie默认存放路径了 。

底层源码:

private String getDomainName(HttpServletRequest request) {
    if (this.domainName != null) {
        return this.domainName;
    } else {
        if (this.domainNamePattern != null) {
            Matcher matcher = this.domainNamePattern.matcher(request.getServerName());
            if (matcher.matches()) {
                return matcher.group(1);
            }
        }

        return null;
    }
}

private String getCookiePath(HttpServletRequest request) {
    return this.cookiePath == null ? request.getContextPath() + "/" : this.cookiePath;
}

场景三、同根域名,不同二级子域名下的项目实现session共享。

 

www.p2p.com 域名中 p2p.com 就是顶级域名,也叫做根域名,也可以叫做一级域名。

 

www不是二级域名。当www被替换了,那么替换的就是二级域名,如下:

Beijing.p2p.com  、 nanjing.p2p.com 、 tianjing.p2p.com

这三个域名中 beijing、tianjing、nanjing 是 二级域名 。

 

知识点补充;

在我们的 redis.conf中:

有一个 daemonize 属性,属性值为 默认为 no,意思是 不后台启动,我们如果想让redis默认后台启动,那么我们就 将我们的 redis.conf文件中的 daemonize属性值修改为 yes 。

然后就可以直接使用

来启动redis了,就不用 再 从最后 加一个 & 号 。

 

这是提供给我们测试使用的三个域名,我们在本地启动对应的tomcat,测试 根域名相同,但是二级域名不同,如何实现session的共享。

每个域名 对应 一个网站,每个网站 部署到两个不同的tomcat服务器上,然后使用nginx进行负载均衡。

 

测试前的准备: 

我们需要修改 hosts文件,让我们的域名 指向到 我们的本地来,也就是 这三个域名对应的ip地址为127.0.0.1 。

 

根域名相同 ,二级域名不同,项目名不同:

      项目名不同 导致的session不共享,我们的解决方案是 将 所有的cookie存放进 /目录下,也就是  根域名/二级域名/ cookie对象  这样子存放我们的cookie 。

   当我们的 二级域名也不一样的时候,只是单单的修改cookie存放位置是不行的,还要修改cookie的存放路径,二级域名不同,根域名相同,那么为了保证每次请求保存的cookie相同,那么需要将cookie存放在公共相同的区域,所以 存放路径为:  根域名

 

存放路径 和 存放位置 相结合:  根域名/   直接将我们的cookie存放在根域名下。

 

 

 

总之,要保证 我们的 session共享,就需要保证cookie是同一个,保证从一个cookie就需要 每次 存 和 每次 取 cookie 都要从 同一个地方 获取和存储,这样保证了cookie的位置。

如:

Beiijing.p2p.com:9100/pay/setsession 创建的session,而且在session中存放了xxx。

如何让我们的 nanjing.p2p.com:9200/pay/getSession 获取到 上面请求创建session,从而取到里面xxx。 这里就需要session共享,单单将session存放进redis是远远不够的,虽然我们的session 被存放进了 一个 共享的位置。 但是每次请求的推送过来的 sessionId被存放进不同位置上的cookie,如果让我们的  cookie也能放在一个共享的区域,每次存和取都能在同一个空间,这样就能保证同一个用户 对应 同一个cookie 对应 同一个session 。

 

 

***如何找到这个 公共的 共享区域呢?

分析我们的 两个域名:

Beijing . p2p . com  对应浏览器中的包:  p2p/com/beijing/

Nanjing . p2p . com  对应浏览器中的包: p2p/com/nanjing/

包结构的公共区域为: P2p/com/

(1)P2p/com 就是 domainName。

(2)/ 就是 cookiePath 。

补充的知识点:

我们的 浏览器有 【 cookie存储 安全保护机制 】。

意思是: 我们不能自己给 上面这两个域名 自定义命名一个 自己命名的共享空间,如:我如果设定 所有的 cookie存放进 abc/com/ 这里。这样子我们的 cookie不会被我们的浏览器存储进去的,因为 在域名-1 下创建出来 cookie对象,最后存放的时候 也要 认祖归宗 落叶归根 到 域名-1 的 【根域名/】 来进行存储。 举个例子:你总不能 向京东 发送一个访问请求,然后将产生的cookie保存到 淘宝的域名根域名下面。

你访问京东产生的cookie只能存放在 京东的域名下,不能存放在淘宝的域名下。这就是cookie存放安全保护机制 。

 

思路:

我们二级域名不一样,那我们就存放在 相同的顶级域名下呗,哪里相同我们就存在哪里呗。

只要 domain+path 保持一致,两次请求cookie的存取位置都是相同的,那么就session共享了。

 *************项目名不相同,二级域名不相同,顶级域名相同,如何实现session共享,步骤如下:

场景四、不同根域名下的项目实现session共享。

如:

这种情况 我们的spring-session框架就不支持了,因为 这种情况:存放 cookie的包 从 根部就已经不相同了,那么根底下的子包就更不相同了,所以domain找不到 相同和共享的包空间了, 所以 我们在 spring-session的核心类中 定义 cookie 存放路径属性的时候,就没办法指定 属性 domainName属性的值,这个值不能指定 我们的session就没有办法共享,所以这种情况 如果 域名从根域名就开始不同了,那么我们的 spring-session就不支持了。

 

我们 通过 www.p2p.com访问我们的服务器 。那么我们的生成的cookie只能往 p2p.com根域名下面去写。

我们通过 www.web.com访问我们的服务器,那么生成的cookie只能往web.com根域名中去写。

以此类推.......

(1)这种情况 我们 可以 使用 【单点登录SSO】来解决我们的session共享问题。

(2)【单点登录SSO】是什么意思呢?

 多个系统应用中(如:天猫,淘宝,支付宝),用户只需要登录一次,其他与这个应用相互信任的应用都会自动登录了。

就好像 我们 登录了 支付宝,那么我们的再登录我们的 淘宝和天猫就可以点击直接登录了。

【单点登录】的实现,我们必须开发一套单点登录系统。一般很大的公司才会有这样的单点登录系统,像阿里巴巴这样才有多个网站和应用。

 

 

Spring - Session框架 的 处理流程:

Spring-session框架需要运行必须拥有而且必不可少的东西:

  1. 全局过滤器:
  2. spring容器::使用 Listener监听器 来 读取我们的 spring 总配置文件 ,然后创建对应的 spring容器。
  3.  我们 用户发出的请求 首先会被我们的 过滤器接收到 。

    过滤器DelegatingFilterProxy 继承了 GenericFilterBean 这个类,然后GenericFilterBean这个类又实现了Filter接口,所以我们的DelegatingFilterProxy 这个类也是一个Filter类,也就是一个过滤器 。

    只要是过滤器就有一下三种方法: init()初始化方法 , doFilter()过滤方法 和 destory()关闭时销毁用的方法 。(1)GenericFilterBean  类的 init初始化方法中:核心代码:

    但是这个方法在我们的GenericFilterBean  类中,方法体中没有添加任何的代码。所以推测出我们的 子类spring-session全局过滤器DelegatingFilterProxy  应该是重写了这个方法initFilterBean()。

    我们子类中 重写这个方法的代码如下:

    当我们的全局过滤器在初始化的时候就会调用这个这个方法。

    全局过滤器刚被加载的时候,会调用init方法,所以也会调用initFilterBean这个方法,这个方法中涉及这段代码:

    刚开始 我们的 delegate对象和targetBeanName对象 肯定都为空。

    这时候 是调用 getFilterName来获取当前这个过滤器的名称,然后将名称赋值为targetBeanName对象。

                          TargetBeanName = filter名称                      ***到这一步我们的 获取到了 Filter的名称了 。

  4. 这段代码是来找 web容器,也就是spring容器,通过FindWebApplicationContext方法。 ****到这一步我们 获取到了spring容器了.方法initDelegate的源码:

    使用 spring容器wac,来获取到一个id为TargetBeanName ,类型为Filter过滤器的 bean对象。

    ***到这一步 我们的获取到了 全局过滤器的 bean对象 。

  5. 初始化都做了什么?

    也就是 我们的 全局过滤器初始化的时候:先获取了 本过滤器的名称,然后有获取到了spring容器,然后根据名称和类型获取到了 过滤器对应的 bean对象 。

    上面的步骤初始化已经完成了

    下面是 发送请求的时候,过滤器会调用 doFilter方法:全局过滤器的父类是一个抽象类,内部没有重写doFilter方法。 那么这个doFilter方法肯定是有 我们的全局过滤器 重写的 。

  6. 全局过滤器中 重写的 doFilter方法,如下:这个dofilter方法中有一个 代理类方法:

  7. 这个代理类方法的内部是:这个代理类方法的内部是:

    意思是:我们调用DelegatingProxy过滤器中的dofilter方法实质上是调用delegate中的dofilter方法。

    其实这个 invokeDelegate方法是一个代理。

    Delegate 就是 我们上面 从init初始化方法中获取到的 目标过滤器,也就是web.xml中配置的过滤器。 也就是最终调用的是 spring容器中bean对象对应的filter中的dofilter方法。 这个代理类 代理的是名字叫做:filter过滤器。 这个过滤器是在我们的spring容器中的一个bean对象。所以真正工作的就是我们的是这个过滤器。

  8. 所以我们开始分析:springsessionrespositoryFilter过滤器: 这个类是我们 Spring-session框架   最核心  的 类 。

  9. 我们在 核心类的父类中找到了这个 真正工作的 过滤器:

  10. @Bean 注解 : Bean的id是方法名,class是方法的返回值类型。

  11. 由上面的源码我们可以看出:

     

  12. 下面我们开始分析中的代码内容: 这个过滤器中不存在 init方法,所以我们的 这个过滤器 不存在 初始化的问题,它继承了父类中的dofilter方法,dofiler中的内容:

  13. 父类dofilter方法中的核心逻辑是:但是这个方法是一个抽象方法:,所以这个方法 应该在我们的子类 中被重写。所以说子类调用父类的dofilter方法实质上是调用了doFilterInternal这个方法。

  14. 子类重写的内容如下:

  15. 继承关系:

  16. 这个是构造自己的 request 和response,将原来的  request 和response覆盖掉。将 request 和response中的方法也给替换掉了,替换成Spring-session框架中的了。

  17. 这个方法就是用来将我们的session存放在redis中。

    这个方法中真正保存session的方法是save()方法:

  18. Save方法中使用的 saveDelta这个方法进行session保存的:

  19. 这个方法中最核心的代码是:

  20. 这里保存的是一个 hash结构的session。因为是使用 putAll进行保存的:

    PutAll这个方法是在 spring-data-redis jar包中。

    然后这个方法中又调用了 jedis 的jar中的方法 hMset()方法,以为操作redis都是用的我么你的jedis进行间接操作的。

  21. 所以保存的session是hash结构:如下图

  22. Spring-Session的执行流程:上面就是 Spring-Session的 全部操作过程。

  23. 最后需要注意的是:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值