Spring-Session 知识点 和 源码分析(上)

一、什么是 session ?  每一个用户 只拥有一个 session 。

(1)Session 的 作用 就是 让我们的 服务器记住 是哪个用户,也就是浏览器访问过我。当我发起一个 会话,我们的服务器端 就会 为我们产生 session,每一个session对应一个sessionID,然后将这个sessionID存放在cookie中然后将cookie存放在我们的浏览器中,然后我们在不关闭浏览器的 条件下,每次发送请求的时候,会默认的无条件的 将我们的 对应这个服务器的 cookie 重新发送给我们的 服务器,这样我们的 服务器就重新难道了 存放在cookie中的sessionID,从而就找到了对应的session,这样就实现了服务器记住我们用户。 我们 在 很多 情况 都会 使用过 cookie 来跟踪 我们的 session,用cookie来存放 sessionID实现跟踪。

 

每一个用户(浏览器) 都对应一个 唯一的独有的 sessionID,通过这个sessionID可以找到唯一对应的 session,我们浏览器的cookie中保存着 第一次 会话 由 服务器传递过来的sessionID。

 

【URL重写技术 解决 我们的session追踪问题】:

如果 我们的 浏览器 禁止 了 向 cookie 中存放数据,那么我们的 服务器 就没有办法 向我们的 cookie中存放 sessionID,那样我们怎样实现 对 session的 跟踪 ?

解答: 这里我们使用到了一个 【URL重写】技术 。

  这个技术 非常好理解: 就是在我们每次发送请求的时候,我们的浏览器会将从服务器哪里接收到的 sessionID 当做一个 请求参数 挂在请求的 最后,如:

Http://localhost:8080/myweb/index.jsp?sessionId=xxxxx 。

这个参数 在我们的 服务器端 会 被接收,我们服务器拿到这个参数之后,用来查找判断这个sessionId对应哪个session。

 

【session 的管理】

(1)session 通常是 交给我们的 tomcat容器管理的。因为我们的tomcat是用来处理动态资源的,所以我们得tomcat中的一个容器专门用来存放我们的 session 。

(2)但是我们的 实际开发中我们不单单只涉及一个 tomcat服务器了,我们是多个tomcat服务器集群的,而且 我们用户和tomcat之间还存在一个nginx,这个nginx是用来做 负载均衡的。这时候就出现了一个问题:

我们的 nginx负载均衡如果不做任何的配置,nginx接收到的请求分发到的tomcat可能是不相同的,因为nginx是根据tomcat的压力和一定的规则来进行分发请求的,所以我们用户 每次发出的请求 处理请求的 tomcat不一定是相同的。

但是我们的 每一个tomcat中都部署 相同的 web项目,所以我们的 用户 在每次 访问我们的页面的时候 虽然是不同的 tomcat进行处理,但是显示的网站是相同 的。

 

因为每次 处理请求的tomcat 是不相同的,就会造成一个问题:session的丢失和不能共享的问题:

因为每次由nginx负载均衡的原因分到的tomcat不相同,所以每次都找不到上一次请求会话sessionId对应的session,所以导致这次的tomcat认为这个请求是新的请求,也是新的一次会话,所以产生新的 session,然后返回新的sessionId,然后代替掉浏览器中的cookie中的sessionId,这样就 每次 用户登录之后 发出一个请求然后刷新一下页面就退出了,就变成了登录又退出登录退出这样的情况。 这就是tomcat集群部署之后存在的问题。

 

****我们怎样解决tomcat集群部署和nginx负载均衡产生的 session不共享丢失的问题呢?

 

【第一种方案】:ip_hash策略:这个策略是 我们nginx负载均衡时在nginx.Conf配置文件下配置的 请求分配策略。

 Ip_hash的原理: 一个ip地址绑定一个tomcat服务器。其中涉及一个计算公式:

Hash函数(  ip地址 )  %  2  == 取余,余数对应tomcat的编号。

所以只要ip地址不变 ,对应的 tomcat编号就不变。

但是 这种 解决 session丢失问题的 方法不好:因为 这样我们的ip地址就不能变了,如果我们今天从山东用的山东移动,明天去了河北,用的河北移动,这样session就没了,意思是ip地址不能改变的,如果你ip变了,就有可能调到了另一个服务器中去了,左脚在北京 右脚进了河北,ip变化了,然后跳转tomcat,然后用户就丢失了session,就退出登录了。

 

【第二种方案】:Spring Session 框架。

这种技术 可以使 session 同步 ,可以对session进行管理,解决集群部署之后 产生的session丢失不同步的 问题。

*****它是怎么实现的?

我们的spring session 以中立的身份,取代和代替 我们 tomcat中的 httpsession,然后将session中的信息数据 存放在 redis 中。

知识点:为什么存放在redis中呢?

我们的redis是将数据存放在内存中的,而且我们可以给数据设置存放的最大生命时间,达到最大生命时间之后就会被自动删除,这一点和我们的session的最大空闲时间是相同相似的。而且我们从内存中读取数据是很快的

作用步骤:

(1)用自身的 Spring-Session 以中立的方式 替换我们的 http-Session。

(2)然后 将 session中的数据信息 存放进 redis 中 。

(3)只需要配置 不需要 写任何的代码,就可以实现springsesson功能。

开发步骤:使用springsession开发步骤。

1.添加springsession的 maven 依赖。 
1)
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
    <version>1.3.1.RELEASE</version>
</dependency>
这个依赖 是对我们的 spring-data-redis依赖的封装。这是依赖是我们springsession所依赖的jar包 。 
2)
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.8.8.RELEASE</version>
</dependency>
这个依赖 是我们 在 spring框架中 【操作redis】时 所需要的依赖。
3)
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
我们的【spring-data-redis】这个依赖 在操作 redis的时候,底层所依赖的jar包是jedis包,实际上 我们【spring-data-redis】包底层是通过【jedis】包来操作我们的redis的。
4)
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>4.3.13.RELEASE</version>
</dependency>
我们的spring-session是在web项目中使用,所以涉及到了web项目和spring,所以需要使用【spring-web】这个依赖。
5)我们在开发web项目时,所需要的一些依赖,可加可不加,加了有提示不报错。
<!-- servlet依赖的jar包start -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>
<!-- servlet依赖的jar包start -->

<!-- jsp依赖jar包start -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.1</version>
</dependency>
<!-- jsp依赖jar包end -->

<!--jstl标签依赖的jar包start -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>
<!--jstl标签依赖的jar包end -->
分别是【servlet依赖】,【jsp依赖】 和 【jstl依赖】。
2.我们在web.xml文件文件中配置springSessionRespositoryFilter过滤器。
SpringSessionRespositoryFilter:springSession仓库过滤器。
我认为这个过滤器的作用是:将 我们 所有的 httpSession替换成 springsession,然后将我们session中的信息数据存放在redis中。 

applicationContext-session.xml配置文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- spring注解、bean的处理器 -->
    <!--这个标签 是用来 激活注解 的-->
    <context:annotation-config/>

    <!-- Spring session 的配置类 -->
    <!--RedisHttpSessionConfiguration这个类专门是用来处理 redis 和 httpsession之间的关系的类-->
    <!--
           我们的RedisHttpSessionConfiguration 这个类中 有很多很多的注解,但是 RedisHttpSessionConfiguration里面的这些方法上类上的注解并没有被激活。
           所以我们需要  <context:annotation-config/> 这个标签激活一下 我们 我们的RedisHttpSessionConfiguration类中的注解。
           如果我们不进行 注解激活,那么我们的RedisHttpSessionConfiguration是不能使用的,会报错。
    -->
    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>


    <!--配置 redis的连接时  所需要的 信息 的-->


    <!--
    spring 封装了 RedisTemplate 对象来进行对redis的各种操作,它支持所有的 redis 原生的 api。
    -->


    <!--
         读取redis.properties属性配置文件
         在我们的 applictionContext-session.xml中引入redis的配置文件,然后就可以使用EL表达式进行赋值了。
    -->
    <context:property-placeholder location="classpath:redis.properties"/>


    <!--下面的配置是为了告诉我们的springsession怎么连接redis的,只需要告诉我们的springsession怎么连接redis,我们都不需要创建RedisTemplate 对象 -->
    <!-- 配置jedis连接工厂,用于连接redis -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.hostName}"/>
        <property name="port" value="${redis.port}"/>
        <property name="password" value="${redis.password}"/>
        <property name="usePool" value="${redis.usePool}"/>
        <property name="timeout" value="${redis.timeout}"/>
    </bean>


</beans>
redis.properties :
redis.hostName=192.168.xxx.xxx
redis.port=6379
redis.password=xxxx
redis.usePool=true
redis.timeout=15000
Web.xml :
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         id="WebApp_ID" version="3.0">

    <!--SpringSessionRespositoryFilter 这是一个spring-session框架的全局过滤器-->
    <!--org.springframework.web.filter.DelegatingFilterProxy就是我们的SpringSessionRespositoryFilter的jar包-->
    <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>
    </filter-mapping>


    <!--
        (1)因为涉及到了spring,所以我们需要spring配置文件,所以需要使用 contextLoader监听器,来监视我们context创建的时刻。
        (2)在context创建的时刻,就将我们的spring配置文件 读取和加载进我们的内存,然后spring容器中就创建好我们需要的实体对象了。
        (3)使用contextLoaderListener 就是为了加载我们的 spring配置文件 。
    -->

    <!-- needed for ContextLoaderListener -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <!-- Bootstraps the root web application context before servlet initialization -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>

我们的session 通过 Spring-session这个框架存放到了我们的redis中。我们session默认最大空闲时间为30minute。

这个类因为有这个注解,所以它有一个定时任务的功能。每个段时间就检查一下我们的session最后一次访问时间,看看我们在最后一次访问之后,有没有过30分钟啊,如果30分钟都没有访问过:

就利用 圈1  和  圈3 这两个 key配合起来 对我们 圈1,2,3 这三个key的删除。

我们真正的session存放在我们的 圈2里面,我们圈1 和圈3 是配合起来在session失效的时候删除这三个key的。

 

Redis中 存放的资源类型:

  1. 动态资源 或 常量:因为我们的 Redis管理数据是以键值对的方式进行管理的。 但是存放的 动态资源和常量我们一般都设置最大生命时间,所以说 在redis中存放的数据 都是不持久的动态资源,虽然可以长久的存放,但是我们人为的设置了最大生命时间了。
  2. 利用spring-session框架 存放我们的 session对象,并且存放的session对象最大空闲会话时间为30分钟。

 

********为什么 我们的 spring-session 框架 可以 让我们的 session对象共享?

由上面 关系图 我们可以看出 :

(1) redis是在【所有】的tomcat服务器的后面。

(2) redis中 存放了所有的 session对象 。

(3)所有的 tomcat 想获取 session 都需要去 redis中获取。

(4)tomcat 从 redis 中 获取到 session 的过程 不需要我们参与,全权由我们的Spring-session操作完成,spring-session帮我们将session拦截到然后存放进我们的 redis中,然后我们想要获取的时候,spring-session通过浏览器传递过来的cookie中的sessionId来自动从redis中帮我们查找对应sessionId的session。

 

总结:

 之所以我们的session共享了,是因为 我们将所有的session存放进了一个公共的空间了,所有的tomcat都可以随便的自动的从这个公共空间获取session了。 原来没有配置springsession的时候 我们 的session是存放在 tomcat中的容器里,这个容器是封闭的,是与其他tomcat没有任何交涉的,所以session不共享。

 

*********我们判断一个用户是否登陆的依据是什么?

我们的用户登录之后 我们的tomcat 就会创建一个session对象,这个session对象是和我们的用户一一对应的,session里面存放着这个用户的所有信息。当我们的用户浏览器发送请求后,请求携带者cookie中的sessionId到达我们的服务器,然后我们的tomcat服务器从redis中获取 sessionId对应的session,如果找到了 说明 这个用户是登陆状态就显示登陆,如果没有找到对应的session对象,说明该用户没有登录,就显示未登录状态。

 

Path是cookie的路径不相同,产生两个session:

P2p项目

Show项目

因为name相同,如果path也相同,那么他们就是同一个session了,键值对嘛,键不能重复。

让不同的项目 共享 session 的 方法 :

指定cookie的存储方式:

利用下面这个类来进行序列化,修改cookie的存储格式:

这个类如何序列化,修改cookie存储格式的:

上面这个类实现了这个接口。

 

配置完成之后:

P2p项目:

Show项目:

这两个项目都是有同一个浏览器用户发出的请求,所以他们的session应该保持一致,如果不一致,就用这种方法让session在两个项目或多个项目中保持共享。

 

域名不相同,项目不相同,怎么实现session共享:

让两个项目的session保存在同一个路径下:

这两个项目的根域名相同,所以都存放在根域名下,路径为:根域名/  

分别制定:domain 和 path这两个值。

Web.com是 一级域名。

项目名 为 二级域名。

包结构:

--Web

            --Com

                      --p2p

                      --show

上面这个地址 是 存放cookie的地址。

Cookie中是用键值对来保存 sessionId的。

Name就是key:session 就代表sessionId

Value 里面就存放着sessionId的值。

Domian + path 就是存放cookie的位置。

 

强行将 cookie放在abc.com这个域名下,这样违反了cookie安全策略,在写到浏览器的时候 会 被禁止,无法写到浏览器上。Cookie只能保存在当前域名下:  cookie的安全策略不允许写到别的域名下。

 

这种情况怎么实现 一个网站登录其他信任网站也登陆呢?

单点故障:如果只有一个节点一个服务器,如果这个节点服务器发生故障了,整个项目网站服务就不能用了。所以至少布置两台服务器节点,但是两台就会出现session不共享的问题,所以我们需要 使用springsession框架实现我们的session共享。

 

 

知识点补充:

Init是初始化方法。以前出现的init方法都是初始化方法。

判断 日志级别是否为 debug级别,如果是debug级别,就打印日志。

Init初始化方法中的核心代码:

代理过滤器中重写这个方法:

GetFilterName()方法底层代码:

意思是:从配置文件中获取到<filter-name>之间的值。

 

获取的是标签对之间的值:

获取 相同名字,相同类型过滤器所涉及的方法 :

 

我们 spring容器中获取到了过滤器的实体bean对象:

<bean id=”springSessionRepositoryFilter” class=”SessionRepositoryFilter的全限定类名”/>

我们调用的是springSessionRepositoryFilter对象中的 doFilter()方法,实质上其实是调用的SessionRepositoryFilter类中的doFilter()方法。

而我们的SessionRepositoryFilter类 中的doFilter()是从我们的OncePerRequestFilter抽象类中继承过来之后重写的,OncePerRequestFilter抽象类中的doFilter()是从我们的Filter接口中得到的。

 

调用 dofilter()方法的过程:

这个Dofilter方法中的核心代码为:

这个方法在我们的 中被重写:代码内容如下:

是这个doFilter()方法的核心代码,因为这段代码和管理session,存储session有关:

这个内部类 继承了 父类中的 getSession方法,并且重写了这个方法。

重写的是【父类】这里面的这些方法:

重写的getSesion方法中的代码内容:核心代码红线(创建一个session对象)

创建session对象的方法的代码内容如下:

创建session对象的方法中的核心代码:

我们将session存放进redis中。

添加属性,如下图:向Map集合中添加属性就相当于下图。redis中的效果图:

保存session的方法:

上图doFilter()方法中有下图这一段代码,专门是用来存储session的:

CommitSession方法代码内容如下:

核心代码中save方法内容如下:

Save核心代码 中 saveDelta()方法内容如下:

这个putAll()方法属于我们的spring-data-redis依赖中的方法,这里已经到了 spring-data-redis中了:

我们spring-data-redis依赖底层向redis存放数据又要依赖 jedis的jar包:

Jedis帮我们保存 session,jedis涉及的方法如下:

上面的源码就是这个图的步骤和知识点。

方法就等于复写了方法。

 

知识点补充:我们不从配置文件中声明bean对象,我们用java代码来实现创建bean对象。

上面 两种方式 都是相当于在 spring容器中 配置和声明了一个 bean对象。

实际开发中,命名 redis中key的规范:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值