Spring Session

Spring Session

一、 HttpSession 回顾

1 什么是 HttpSession

是 JavaWeb 服务端提供的用来建立与客户端会话状态的对象。

二、 Session 共享

1 什么是 Session 共享

是指在一个浏览器对应多个 Web 服务时,服务端的 Session 数据需要共享。

2 Session 共享应用场景

  1. 单点登录
  2. Web 服务器集群等场景都需要用到

3 Session 共享常见的解决方案

3.1Session 复制

通过对应用服务器的配置开启服务器的 Session 复制功能,在集群中的几台服务器之间同步 Session 对象,使得每台服务器上都保存所有的 Session 信息,这样任何一台宕机都不会导致 Session 的数据丢失,服务器使用 Session 时,直接从本地获取。这种方式的缺点也比较明显。因为 Session 需要时时同步,并且同步过程是由应用服务器来完成,由此对服务器的性能损耗也比较大。

3.2Session 绑定

利用 hash 算法,比如 nginx 的 ip_hash,使得同一个 Ip 的请求分发到同一台服务器上。
这种方式不符合对系统的高可用要求,因为一旦某台服务器宕机,那么该机器上的
Session 也就不复存在了,用户请求切换到其他机器后没有 Session,无法完成业务处理。

3.3 利用 Cookie 记录 Session

Session 记录在客户端,每次请求服务器的时候,将 Session 放在请求中发送给服务器,
服务器处理完请求后再将修改后的 Session 响应给客户端。这里的客户端就是 cookie。
利用 cookie 记录 Session 的也有缺点,比如受 cookie 大小的限制,能记录的信息有限,
安全性低,每次请求响应都需要传递 cookie,影响性能,如果用户关闭 cookie,访问就不正常。

3.4Session 服务器

Session 服务器可以解决上面的所有的问题,利用独立部署的 Session 服务器统一管理Session,服务器每次读写 Session 时,都访问 Session 服务器。
对于 Session 服务器,我们可以使用 Redis 或者 MongoDB 等内存数据库来保存 Session中的数据,以此替换掉服务中的 HttpSession。达到 Session 共享的效果。

三、 是 什么是 Spring Session

Spring Session 是 Spring 的项目之一。Spring Session 提供了一套创建和管理 Servlet HttpSession 的方案,默认采用外置的 Redis 来存储 Session 数据,以此来解决 Session 共享的问题。

四、 Spring Session 的使用

1 安装 Redis

第一步 需要在 linux 系统中安装 gcc
命令:yum install -y gcc-c++

第二步 需要将下载好的 redis 压缩包添加到 linux 服务器中
版本:redis-3.0.0.tar.gz
redis 的版本:副版本号奇数版本号是测试版,不建议在生产环境中使用。
偶数版本时稳定版建议在生产环境中使用。
3.0 版本更新比较大。集成了集群技术

第三步 解压压缩包
命令:tar -zxvf redis…

第四步 编译 redis
命令:进入 redis 的解压完毕的根目录下 执行命令:make

第五步 安装 redis
命 令 : 进 入 redis 的 解 压 完 毕 的 根 目 录 下 , 执 行 命 令 : make install
PREFIX=/usr/local/redis

第六步:启动 redis
1,前端启动
在 bin 目录下执行命令: ./redis-server (ctrl+c)退出 redis

2.后端启动
(1)先将 redis 解压目录下的 redis.conf 文件拷贝到 安装好的 redis 的 bin 目录下
命令:cp redis.conf /usr/local/redis/bin

(2)修改拷贝过来的 redis.conf 配置文件
命令:vim redis.conf
将 daemonize no 改为 yes

(3)启动 redis
在 bin 目录下执行命令:./redis-server redis.conf

(4)查看 redis 启动是否成功
输入命令:ps aux|grep redis

(5) 关闭 redis 的命令
./redis-cli shutdown
第七步:测试 redis
在 bin 目录下启动 redis 自带的客户端 ./redis-cli
常见 redis 命令:
ping—>pong

2 搭建案例环境

2.1 版本介绍

JDK:1.8
Spring Boot:2.1.6.RELEASE
Spring Session:Bean-SR3

2.2 创建项目

创建父工程 spring_session

在这里插入图片描述

创建模块 session_service1

在这里插入图片描述

创建模块 session_service2

在这里插入图片描述

在这里插入图片描述

2.3 修改 POM 文件添加坐标依赖

修改父工程 POM 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjsxt</groupId>
    <artifactId>spring_session</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>session_service1</module>
        <module>session_service2</module>
    </modules>

    <!--如果不用parent标签,则传递给子模块时依赖会传递过去但是插件无法传递-->
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.session</groupId>
                <artifactId>spring-session-bom</artifactId>
                <version>Bean-SR3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!--web Starter-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--Redis Starter-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-redis</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>
        <!--Spring session data redis-->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>
        <!--Lettuce 是一个基于 Netty 的 NIO 方式处理 Redis 的技术 -->
        <dependency>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </dependency>
    </dependencies>

</project>

BUG:

在这里插入图片描述

3 添加配置文件

3.1session_service1

在这里插入图片描述

#服务端口
server:
  port: 8080
#Redis配置
spring:
  redis:
    host: 192.168.88.101
    port: 6379
    password:          #如果开启了用户认证给密码,为开启就为空
    database: 0       #指定操作哪个库
3.2session_service2
#服务端口
server:
  port: 8081
#Redis配置
spring:
  redis:
    host: 192.168.88.101
    port: 6379
    password:          #如果开启了用户认证给密码,为开启就为空
    database: 0       #指定操作哪个库

4 创建启动类

4.1session_service1
package com.bjsxt.session.test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@SpringBootApplication
@EnableRedisHttpSession//Session调用会自动去redis存取
public class ServiceApplication1 {
    public static void main(String[] args) {
        SpringApplication.run(ServiceApplication1.class, args);
    }
}
4.2session_service2
package com.bjsxt.session.test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@SpringBootApplication
@EnableRedisHttpSession//Session调用会自动去redis存取
public class ServiceApplication2 {
    public static void main(String[] args) {
        SpringApplication.run(ServiceApplication2.class, args);
    }
}

在这里插入图片描述

5 编写测试代码测试效果

5.1session_service1
5.1.1 创建 Controller
package com.bjsxt.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

@RestController
@RequestMapping("service1")
public class WebController {

    @RequestMapping("/setMsg")
    public String setMsg(HttpSession session, String msg) {
        session.setAttribute("msg", msg);
        return "ok";
    }
}
5.2session_service2
5.2.1 创建 Controller
package com.bjsxt.session.test.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

@RestController
@RequestMapping("service2")
public class WebController {

    @RequestMapping("/getMsg")
    public String setMsg(HttpSession session) {
        String msg = (String) session.getAttribute("msg");
        return msg;
    }
}
5.2.2测试

在这里插入图片描述

在这里插入图片描述

spring session 将session同步到redis中用的是JDK的序列化器所以是乱码

在这里插入图片描述

BUG:

SpringBoot启动类必须和控制器所在的包在同一级目录下否则项目可以成功启动但是不能访问资源

在这里插入图片描述

6 共享自定义对象

6.1 创建 Users

实现序列化接口 放进去是序列化,拿出来是反序列化

package com.bjsxt.session.test.domin;

import java.io.Serializable;

/**
 * 实体实现序列化接口才能被序列化到redis中才能被session共享
 */
public class Users implements Serializable {
    private String username;
    private String userpwd;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUserpwd() {
        return userpwd;
    }

    public void setUserpwd(String userpwd) {
        this.userpwd = userpwd;
    }
}
6.2 修改 session_service1 的 controller
/**
 * 获取user信息保存到HTTPSession中
 */
@RequestMapping("/addUsers")
public String addUsers(HttpSession session, Users users) {
    session.setAttribute("u", users);
    return "OK";
}
6.3 修改 session_service2 的 controller
/**
 * 获取HTTPSession中的users对象
 */
@RequestMapping("/getUsers")
public Users getUsers(HttpSession session) {
    Users users = (Users) session.getAttribute("u");
    return users;
}
6.4测试

在这里插入图片描述

在这里插入图片描述

7 Spring Session 的 的 Redis 存储结构

spring:session:expirations:(Set 结构)用户 ttl 过期时间记录
这个 k 中的值是一个时间戳,根据这个 Session 过期时刻滚动至下一分钟而计算得出。
这个 k 的过期时间为 Session 的最大过期时间 + 5 分钟。 session默认过期时间是30分钟

spring:session:sessions:(Hash 结构)

maxInactiveInterval:过期时间间隔

creationTime:创建时间

lastAccessedTime:最后访问时间

sessionAttr:Attributes 中的数据

存储 Session 的详细信息,包括 Session 的过期时间间隔、最后的访问时间、attributes的值。这个 k 的过期时间为 Session 的最大过期时间 + 5 分钟。

spring:session:sessions:expires:(String 结构)过期时间记录
这个 k-v 不存储任何有用数据,只是表示 Session 过期而设置。
这个 k 在 Redis 中的过期时间即为 Session 的过期时间间隔。

8 设置 Session 的失效时间

8.1在注解中设置失效时间

在这里插入图片描述

删除掉之前的spring key

在这里插入图片描述

package com.bjsxt.session.test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@SpringBootApplication
@EnableRedisHttpSession(maxInactiveIntervalInSeconds=20)    //Session调用会自动去redis存取  设置失效时间为20秒
public class ServiceApplication1 {
    public static void main(String[] args) {
        SpringApplication.run(ServiceApplication1.class, args);
    }
}

重启服务刷新请求

在这里插入图片描述

失效时间一到redis里面的key就被删除了,再去取数据就取不到了

在这里插入图片描述

访问service2 虽然拿不到数据但是redis中针对service2 又生成一条数据

在这里插入图片描述

现在再刷新service1 失效时间还是1800秒,因为相当于先用service2项目来创建的缓存session,service1即便设置了失效时间,任然以先创建的session的失效时间作为最终的失效时间,所以就不是20秒

如果要保证每个服务的session失效时间都一样就需要再每个服务都设置失效时间,否则就会出现失效时间不一致的问题

在这里插入图片描述

9 @EnableRedisHttpSession 注解讲解

9.1maxInactiveIntervalInSeconds

设置 Session 的失效时间,单位为秒。默认(1800 秒)30 分钟。

9.2redisNamespace

为键定义唯一的命名空间。该值用于通过更改前缀与默认 spring:session 隔离会话

防止多个服务在取值的时候取错值(redis中缓存了多个session)

在这里插入图片描述

启动刷新服务

在这里插入图片描述

刷新service2服务拿不到数据了

因为,service2中并没有设置redisnamespace,未设置默认就是(冒号分隔树形结构)spring:sessions:。。。。。一大串,拿着这一大串默认的key去redis中获取是取不到的,redis中没有这个key

在这里插入图片描述

这个key是service2中所创建缓存中的内容

在这里插入图片描述

设置成与service1 相同的namespace才能取到值

在这里插入图片描述

在这里插入图片描述

9.3redisFlushMode

Redis 会话的刷新模式。默认值为“保存”

在这里插入图片描述

9.4cleanupCron

过期会话清理作业的 cron 表达式。默认值(“0 * * * * *”)每分钟运行(检查)一次。 定时器表达式

在这里插入图片描述

10 更换 Spring Session 的序列化器

Spring Session 中默认的序列化器为 jdk 序列化器,该序列化器效率低下,内存再用大。
我们可以根据自己的需要更换其他序列化器,如 GenericJackson2JsonRedisSerializer 序列化器。

10.1在配置类中创建自定义序列化器
package com.bjsxt.session.test.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisSessionConfig {

    /**
     * 更换默认的序列化器
     *springSession存储在SpringIOC容器中的key为:  springSessionDefaultRedisSerializer
     * springSessionDefaultRedisSerializer:默认存贮序列化器的key
     *
     * @Bean("springSessionDefaultRedisSerializer") :给这个key赋一个新的值 —— new GenericJackson2JsonRedisSerializer();
     *
     * 这样就换了一个序列化器
     */
    @Bean("springSessionDefaultRedisSerializer")
    public RedisSerializer defaultRedisSerializer() {
        return getSerializer();
    }

    /**
     * 定义序列化器
     */
    private RedisSerializer getSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }

}
10.2 测试

删掉redis中的所有key然后重启服务

在这里插入图片描述

如果有多个服务的序列化器不一样那么会报错

在这里插入图片描述

将service2 的序列化器和service1设置成一样

package com.bjsxt.session.test.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisSessionConfig {

    /**
     * 更换默认的序列化器
     *springSession在SpringIOC容器中存储的key为:  springSessionDefaultRedisSerializer
     * springSessionDefaultRedisSerializer:默认存贮序列化器的key
     *
     * @Bean("springSessionDefaultRedisSerializer") :给这个key赋一个新的值 —— new GenericJackson2JsonRedisSerializer();
     *
     * 这样就换了一个序列化器
     */
    @Bean("springSessionDefaultRedisSerializer")
    public RedisSerializer defaultRedisSerializer() {
        return getSerializer();
    }

    /**
     * 定义序列化器
     */
    private RedisSerializer getSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }

}

在这里插入图片描述

测试具有关联关系的实体能否被序列化器序列化

在这里插入图片描述

package com.bjsxt.session.test.domin;

import java.io.Serializable;

public class Roles implements Serializable {
    private Integer roleid;
    private String rolename;

    public Integer getRoleid() {
        return roleid;
    }

    public void setRoleid(Integer roleid) {
        this.roleid = roleid;
    }

    public String getRolename() {
        return rolename;
    }

    public void setRolename(String rolename) {
        this.rolename = rolename;
    }
}

在这里插入图片描述

重启服务刷新请求

在这里插入图片描述

必须将相关联的所有实体都拷贝到service2 中才能获取到session中的信息

在这里插入图片描述
重启服务刷新请求

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值