Spring Session的使用示例

目录
  • Session
  • Session常用解决方案
  • Spring-Session
    • 测试代码
    • 基于数据库的Spring-Session
    • 基于Redis的Spring-Session
  • 总结

Session

Http协议是无状态的,这样对于服务端来说,没有办法区分是新的访客还是旧的访客。但是,有些业务场景,需要追踪用户多个请求,此时就需要Session。关于session的百度百科session

Session:在计算机中,尤其是在网络应用中,称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web页时,如果该用户还没有会话,则Web服务器将自动创建一个 Session对象。当会话过期或被放弃后,服务器将终止该会话

核心特点:

  1. 服务端存储
  2. 会过期

Session常用解决方案

对于Session的常用解决方案,可以划分为三种。

  • 负载均衡方式

借助负载均衡设备或者模块,将指定的Session始终路由到同一台机器即可,如Nginx。

  • 副本复制方式

利用服务器节点间的副本复制方式,保证集群所有节点拥有的Session数据一致。

  • 集中存储方式

引入第三方存储,将Session数据集中存储到外部存储中,如Redis或者数据库等。

本文介绍的Spring-Session是采用第三种,集中存储的方式。

Spring-Session

核心组成模块

  • Spring Session Core

提供Spring Session核心的功能和API

  • Spring Session Data Redis

提供基于Redis的SessionRepository以及配置

  • Spring Session JDBC

提供基于关系型数据库的SessionRepository以及配置

  • Spring Session Hazelcast

提供基于Hazelcast的SessionRepository以及配置

测试代码

controller提供三个接口,分别对应Session的获取、保存和清理

@GetMapping("/")
public String process(Model model, HttpSession session) {
  @SuppressWarnings("unchecked")
  List<String> messages = (List<String>) session.getAttribute("springSession");

  if (messages == null) {
    messages = new ArrayList<>();
  }
  model.addAttribute("sessionMessages", messages);

  return "sessionTest";
}

@PostMapping("/persistSession")
public String persistMessage(@RequestParam("msg") String msg, HttpServletRequest request) {
  @SuppressWarnings("unchecked")
  List<String> messages = (List<String>) request.getSession().getAttribute("springSession");
  if (messages == null) {
    messages = new ArrayList<>();
    request.getSession().setAttribute("springSession", messages);
  }
  messages.add(msg);
  request.getSession().setAttribute("springSession", messages);
  return "redirect:/";
}

@PostMapping("/destroySession")
public String destroySession(HttpServletRequest request) {
  request.getSession().invalidate();
  return "redirect:/";
}

sessionTest.html对应页面操作

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Spring Boot Session Example</title>
</head>
<body>
<div>
    <form th:action="@{/persistSession}" method="post">
        <textarea name="msg" cols="40" rows="2"></textarea>
        <br> <input type="submit" value="保存" />
    </form>
</div>
<div>
    <h2>session列表</h2>
    <ul th:each="message : ${sessionMessages}">
        <li th:text="${message}">message</li>
    </ul>
</div>

<div>
    <form th:action="@{/destroySession}" method="post">
        <input type="submit" value="清空" />
    </form>
</div>
</body>
</html>

基于数据库的Spring-Session

1.引入maven依赖

使用MySQL存储,所以引入了MySQL。

涉及到SpringBoot JDBC的配置,引入了Spring Boot JDBC Starter。

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-core</artifactId>
  <version>2.5.0</version>
</dependency>

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-jdbc</artifactId>
  <version>2.5.0</version>
</dependency>

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.18</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

注意:

No session repository could be auto-configured, check your configuration (session store type is 'jdbc')

如果存在这个报错,是因为没有引入spring-boot-starter-jdbc,引入即可。

2.配置application.properties文件

主要包含两部分,数据库的配置以及Spring Session Jdbc配置。

# 配置数据源相关内容
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/spring_learn?createDatabaseIfNotExist=true&autoReconnect=true&useSSL=false
spring.datasource.username=xxx
spring.datasource.password=xxx
spring.datasource.initialization-mode=always

# session类型选择jdbc
spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=always
# 指定表名
#spring.session.jdbc.table-name=SESSIONS
# 超时时间
spring.session.timeout=180s

3.数据库存储解析

默认情况下,数据库中会创建2张表。SPRING_SESSION和SPRING_SESSION_ATTRIBUTION。

SPRING_SESSION用于存在session自身的一些属性,如创建时间、过期时间等,详细schema如下。

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) DEFAULT NULL,
  PRIMARY KEY (`PRIMARY_ID`),
  UNIQUE KEY `SPRING_SESSION_IX1` (`SESSION_ID`),
  KEY `SPRING_SESSION_IX2` (`EXPIRY_TIME`),
  KEY `SPRING_SESSION_IX3` (`PRINCIPAL_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC

SPRING_SESSION_ATTRIBUTION用于存储session相关联的属性,schema如下。

CREATE TABLE `SPRING_SESSION_ATTRIBUTES` (
  `SESSION_PRIMARY_ID` char(36) NOT NULL,
  `ATTRIBUTE_NAME` varchar(200) NOT NULL,
  `ATTRIBUTE_BYTES` blob NOT NULL,
  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 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC

4.测试执行

SPRING_SESSION中的数据

SPRING_SESSION_ATTRIBUTION中的数据。

基于Redis的Spring-Session

几乎同样的步骤

maven依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>

application.properties配置

spring.session.store-type=redis
spring.redis.host=127.0.0.1
spring.redis.port=6379

结果分析

一次请求后,多了三个属性,分析如下。

key 类型 用途 value
spring:session:sessions:expires:${sessionId} string 判断sesssion是否存在
spring:session:sessions:${sessionId} hash session相关的属性,包括有效期、创建时间、具体属性等 creationTime/lastAccessedTime/sessionAttr/maxInactiveInterval
spring:session:expirations:1623656160000 set 存储待过期的sessionId列表 key: 过期的时间戳;value: 在这个时间戳将要过期的expire key列表。

在访问时,先通过第一个key,判断session是否存在以及是否过期。如果没有过期,可以通过第二个key获取或者更新对应的session详情。

对于第三个key,实际上Spring-Session-Redis会有特殊的用途,主要是为了Redis的keySpace-notificationhttps://redis.io/topics/notifications。核心目的是为了确保过期的session一定要触发过期事件。关于这方面的解释,可以看一下RedisIndexedSessionRepository中的注释。

订阅Spring-Session的相关事件

有些时候,我们比较关心Session的创建或者销毁事件,做一些特殊的处理或者记录。基于Redis的Spring-Session利用Spring Event将该事件发布出来,我们可以使用EventListener监听做处理。

@Component
@Slf4j
public class AnnotationDrivenEventListener {

  @EventListener
  public void handleSessionCreated(SessionCreatedEvent sessionCreatedEvent) {
    String sessionId = sessionCreatedEvent.getSessionId();
    log.info("session id:{} created", sessionId);
  }

  @EventListener
  public void handleSessionDestroyed(SessionDestroyedEvent sessionDestroyedEvent) {
    String sessionId = sessionDestroyedEvent.getSessionId();
    log.info("session id:{} destroyed", sessionId);
  }
}

总结

Spring Session提供了非常便利的,基于关系型数据库或者Redis的Session解决方案。

Redis版访问速度快,基于Redis的过期策略,保证过期数据会被删除,同时支持事件订阅。

数据库版直接基于数据库,无需单独引入其他存储。但是访问速度相对较慢,过期数据需要依赖应用程序自身进行删除。同时没有提供事件订阅能力。

以上就是Spring Session的使用示例的详细内容,更多关于Spring Session的使用的资料请关注我们其它相关文章!

(0)

相关推荐

  • Springboot中登录后关于cookie和session拦截问题的案例分析

    一.前言 1.简单的登录验证可以通过Session或者Cookie实现. 2.每次登录的时候都要进数据库校验下账户名和密码,只是加了cookie 或session验证后:比如登录页面A,登录成功后进入页面B,若此时cookie过期,在页面B中新的请求url到页面c,系统会让它回到初始的登录页面.(类似单点登录sso(single sign on)). 3.另外,无论基于Session还是Cookie的登录验证,都需要对HandlerInteceptor进行配置,增加对URL的拦截过滤机制. 二.

  • 浅谈Spring Session工作原理

    目录 1.引入背景 2.使用方法 3.工作流程 4.缓存机制 5.事件订阅 6.总结 1.引入背景 HTTP协议本身是无状态的,为了保存会话信息,浏览器Cookie通过SessionID标识会话请求,服务器以SessionID为key来存储会话信息.在单实例应用中,可以考虑应用进程自身存储,随着应用体量的增长,需要横向扩容,多实例session共享问题随之而来. 应用部署在Tomcat时,session是由Tomcat内存维护,如果应用部署多个实例,session就不能共享.Spring Ses

  • SpringBoot2.x 整合Spring-Session实现Session共享功能

    1.前言 发展至今,已经很少还存在单服务的应用架构,不说都使用分布式架构部署, 至少也是多点高可用服务.在多个服务器的情况下,Seession共享就是必须面对的问题了. 解决Session共享问题,大多数人的思路都是比较清晰的, 将需要共享的数据存在某个公共的服务中,如缓存.很多人都采用的Redis,手动将Session存在Redis,需要使用时,再从Redsi中读取数据.毫无疑问,这种方案是可行的,只是在手动操作的工作量确实不少. LZ在这里采用的Spring-Session来实现.它使用代理

  • Springboot Session共享实现原理及代码实例

    在传统的单服务架构中,一般来说,只有一个服务器,那么不存在 Session 共享问题,但是在分布式/集群项目中,Session 共享则是一个必须面对的问题,先看一个简单的架构图: 在这样的架构中,会出现一些单服务中不存在的问题,例如客户端发起一个请求,这个请求到达 Nginx 上之后,被 Nginx 转发到 Tomcat A 上,然后在 Tomcat A 上往 session 中保存了一份数据,下次又来一个请求,这个请求被转发到 Tomcat B 上,此时再去 Session中获取数据,发现没有

  • SpringBoot中实现分布式的Session共享的详细教程

    一. SpringBoot中实现Session共享 1. 创建web项目 我们按照之前的经验,创建一个web程序,并将之改造成Spring Boot项目,具体过程略. 2.添加依赖包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>

  • 多个SpringBoot项目采用redis实现Session共享功能

    有时我们可能有多个不同的Web应用,可以相互调用,这时如果每个应用都有自己的session,那用户跳转到另一个应用时就又需要登陆一次,这样会带来很不好的体验,因此我们需要在不同的应用中共享session.这里,我们采用redis来实现. 前置说明 由于只用到redis和springboot的整合,所以只能实现一个URL下的不同端口的应用之间的session共享,如果连应用名称都完全不同的两个应用要实现session共享,在这个基础上还需要使用到Nginx,这种方式我暂时还没有试过.(Spring

  • SpringCloud Feign转发请求头(防止session失效)的解决方案

    微服务开发中经常有这样的需求,公司自定义了通用的请求头,需要在微服务的调用链中转发,比如在请求头中加入了token,或者某个自定义的信息uniqueId,总之就是自定义的一个键值对的东东,A服务调用B服务,B服务调用C服务,这样通用的东西如何让他在一个调用链中不断地传递下去呢?以A服务为例: 方案1 最傻的办法,在程序中获取,调用B的时候再转发,怎么获取在Controller中国通过注解获取,或者通过request对象获取,这个不难,在请求B服务的时候,通过注解将值放进去即可:简代码如下: 获取

  • SpringBoot 如何实现Session共享

    HttpSession,是通过Servlet容器创建并进行管理的,创建成功以后将会保存在内存中,这里将会使用Redis解决session共享的问题. 创建项目 添加pom 添加相关的maven <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3

  • spring-redis-session 自定义 key 和过期时间

    对于分布式应用来说,最开始遇到的问题就是 session 的存储了,解决方案大致有如下几种 使用 spring-session 它可以把 session 存储到你想存储的位置,如 redis,mysql 等 使用 JWTs ,它使用算法来验证 token 的合法性,是否过期,并且 token 无法被伪造,信息也是无法被篡改的 本文内容主要说 spring-session 使用 redis 来存储 session ,实现原理,修改过期时间,自定义 key 等 spring-session 对于内部

  • springboot中的springSession的存储和获取实现

    利用redis进行springSession的存储: 存储: // 在session中保存用户信息 HttpSession session = httpRequest.getSession(true); session.setAttribute(Constants.SESSION_KEY_USER + userToken, user); // 存储sessionId redisService.hmSet(Constants.SESSION_ID_KEY , userToken, session.

  • SpringBoot+SpringSession+Redis实现session共享及唯一登录示例

    最近在学习springboot,session这个点一直困扰了我好久,今天把这些天踩的坑分享出来吧,希望能帮助更多的人. 一.pom.xml配置 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency>

随机推荐