开心一刻

快 8 点了,街边卖油条的还没来,我只能给他打电话

大哥在电话中说到:劳资卖了这么多年油条,从来都是自由自在,自从特么认识了你,居然让我有了上班的感觉!

Session 共享

SpringBoot session 共享配置,我相信你们都会,但出于负责的态度,我还是给你们演示一遍

  1. 添加依赖

    <?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.qsl</groupId>
    <artifactId>spring-boot-session-demo</artifactId>
    <version>1.0-SNAPSHOT</version> <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.18</version>
    </parent> <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties> <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <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>
    </dependencies>
    </project>
  2. 添加配置

    文件配置 application.yml

    spring:
    session:
    store-type: redis
    redis:
    timeout: 3000
    password: 123456
    host: 10.5.108.226
    port: 6379

    注解配置

    @SpringBootApplication
    @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 900, redisNamespace = "session-demo")
    public class SessionApplication { public static void main(String[] args) {
    SpringApplication.run(SessionApplication.class, args);
    }
    }

    两个配置项需要说明下

    maxInactiveIntervalInSeconds:session 有效时长,单位是秒,示例中 session 有效时长是 900s

    redisNamespace:redis 命名空间,即将 session 信息存于 redis 的哪个命名空间下,没有会创建,示例中是 session-demo

  3. 操作 session

    为了简化,直接提供接口设置和访问 session

    @RestController
    @RequestMapping("hello")
    public class HelloController {
    @GetMapping("/set")
    public String set(HttpSession session) {
    session.setAttribute("user", "qsl");
    return "qsl";
    }
    @GetMapping("/get")
    public String get(HttpSession session) {
    return session.getAttribute("user") + "";
    }
    }

至此,搭建就算完成了,启动后访问

http://localhost:8080/hello/set

然后去 redis 看 session 信息

有效时长为什么是 870 而不是 900,请把头伸过来,我悄悄告诉你

我就问你们,SpringBoot Session 共享是不是很简单?但就是这么简单的内容,竟然有人往里面下毒,而我很不幸的成了那个中毒之人,如果不是我有绝招,说不定就噶过去了,具体细节且听我慢慢道来

配置不生效

实际项目中,我也是按如上配置的,可 redis 中的存放内容却是

从结果来看,session 确实是共享了,但为什么 maxInactiveIntervalInSecondsredisNamespace 配置都未生效?我还特意去对比了另外一个项目,一样的配置流程,那个项目的 命名空间有效时长 都是正常生效的,而此项目却未生效,这就让我彻底懵圈了

debug 源码

该尝试的都尝试了,maxInactiveIntervalInSecondsredisNamespace 始终不生效,没有办法了,只能上绝招了

debug 调试源码

问题又来了:断点打在哪?有两个地方需要打断点

  1. RedisHttpSessionConfiguration#sessionRepository

    跟进到 @EnableRedisHttpSession 注解里面,会看到 @Import(RedisHttpSessionConfiguration.class),跟进 RedisHttpSessionConfiguration,会看到被 @Bean 修饰的 sessionRepository 方法,正常情况下,SpringBoot 启动过程中会调用该方法,我们在该方法第一行打个断点

  2. SpringHttpSessionConfiguration#springSessionRepositoryFilter

    注意看 RedisHttpSessionConfiguration 的完整定义

    @Configuration(proxyBeanMethods = false)
    public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
    implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware

    它继承 SpringHttpSessionConfiguration,跟进去你会发现有个被 @Bean 修饰的 springSessionRepositoryFilter 方法,正常情况下,SpringBoot 启动过程中也会调用该方法,我们也在该方法第一行打个断点

打完断点后,重新以 debug 方式进行启动,我们会发现最先来到 springSessionRepositoryFilter 的断点

然后我们按 F9,会发现项目启动完了都没有来到 RedisHttpSessionConfiguration#sessionRepository 的断点,这是为什么?SpringHttpSessionConfiguration#springSessionRepositoryFilter 方法有个参数 SessionRepository<S> sessionRepository,它依赖 RedisIndexedSessionRepository 实例,也就说 RedisHttpSessionConfiguration#sessionRepository 应该被先调用,sessionRepository 方法都没有被调用,那 springSessionRepositoryFilter 方法的参数实例是个什么鬼?我们再次以 debug 方式启动

怎么是 RedisOperationsSessionRepository,为什么不是 RedisIndexedSessionRepository ?我们来看看 RedisOperationsSessionRepository

它继承了 RedisIndexedSessionRepository,重点是它被 @Deprecated 了呀,怎么还会创建该类型的实例,它是哪里被实例化了?按住 ctrl 键,鼠标左击 RedisOperationsSessionRepository

点进 RedisConfig 一看吓一跳

一看提交记录,竟然是 2021-09-26 提交的,一看提交人,好家伙,早就离职了!

我估摸着,当初想做 session 共享,但是开发到了一半,直接离职了,你说你离职就离职吧,为什么要提交这一半代码,真的是,气的我牙都咬碎了!

注释掉 RedisConfig 后重启,一切恢复正常,maxInactiveIntervalInSecondsredisNamespace 都正常生效;实际工作开发中,此事就完结了,不要再去细扣了,除非你确实闲的蛋疼。但话说回来,你们都来看博客了,那确实是闲,既然你们这么闲,那我们继续扣一扣,扣什么呢

为什么我们指定 RedisOperationsSessionRepository 后,RedisHttpSessionConfiguration#sessionRepository 方法不被调用,而且 maxInactiveIntervalInSeconds 、redisNamespace 不生效

  1. RedisHttpSessionConfiguration#sessionRepository 为什么没被调用

    不管是我们自定义的 RedisConfig#redisOperationsSessionRepository,还是 SpringBoot 的 RedisHttpSessionConfiguration#sessionRepository,都会在启动过程中被 SpringBoot 解析成 BeanDefinition,至于如何解析的,这就涉及到 @Configuration 的解析原理,不了解的可以先看看:spring-boot-2.0.3源码篇 - @Configuration、Condition与@Conditional 。另外,BeanDefinition 的扫描是有先后顺序的,详情请看:三探循环依赖 → 记一次线上偶现的循环依赖问题

    回到我们的案例,那么 RedisConfig#redisOperationsSessionRepository 会先于 RedisHttpSessionConfiguration#sessionRepository 扫描成 BeanDefinition

    而紧接着的 bean 实例化就是按着这个顺序进行的,也就说 RedisConfig#redisOperationsSessionRepository 会先被调用;我们把重点放到名字叫做 sessionRepository 的 bean 的实例化过程上。这里补充个 debug 小技巧,因为 bean 很多,而我们只关注其中某个 bean 的实例化,可以借助 IDEA 的 Condition 来实现

    然后按 F9,会直接来到 sessionRepository 实例化过程,然后经过 getBean(String name) 来到 doGetBean

    跟进 transformedBeanName 方法,继续跟进来到 canonicalName

    这是重点,大家看仔细了,根据别名递归读取主名,返回最后那个主名,是不是这么个逻辑?然而新的疑问又来了

    哪来的别名、主名呀

    常规情况下,bean 只有一个名字,也就是主名,使用 @Bean 的时候如果没有指定名字,那么名字默认就是方法名,而如果指定了名字就采用指定的名字;支持指定多个名字,第一个是主名,后面的都是别名

    所以,根据别名 sessionRepository 就得到了 redisOperationsSessionRepository 这个主名

    而名叫 redisOperationsSessionRepository 的 bean 已经被创建过了,类型是 RedisOperationsSessionRepository,直接从容器中获取,然后返回;所以 RedisHttpSessionConfiguration#sessionRepository 没被调用,你们明白了吗?

    回到最初的问题,如果不注释 RedisConfig,而只是拿掉别名 sessionRepository

    @Configuration
    public class RedisConfig { @Autowired
    private RedisTemplate redisTemplate; @Bean({"redisOperationsSessionRepository"})
    public RedisOperationsSessionRepository redisOperationsSessionRepository() {
    return new RedisOperationsSessionRepository(redisTemplate);
    }
    }

    问题能不能得到解决?

    总结下

    根据扫描先后循序,RedisConfig#redisOperationsSessionRepository 的 BeanDefinition 排在 RedisHttpSessionConfiguration#sessionRepository 前面,所以 bean 实例创建的时候,RedisOperationsSessionRepository 实例会被先创建,而这个实例的别名 sessionRepository 正好与 RedisHttpSessionConfiguration#sessionRepository 名字重复,所以不会调用 RedisHttpSessionConfiguration#sessionRepository 来创建实例,而是直接返回已经创建好的 RedisOperationsSessionRepository 实例

  2. maxInactiveIntervalInSeconds 、redisNamespace 为什么不生效

    大家注意看 RedisConfig

    @Configuration
    public class RedisConfig { @Autowired
    private RedisTemplate redisTemplate; @Bean({"redisOperationsSessionRepository", "sessionRepository"})
    public RedisOperationsSessionRepository redisOperationsSessionRepository() {
    return new RedisOperationsSessionRepository(redisTemplate);
    }
    }

    试问如何让 maxInactiveIntervalInSeconds 、redisNamespace 生效?

    既然官方已经把 RedisOperationsSessionRepository 废弃了,我们就不要纠结它了,直接不用它!

总结

  1. SpringBoot Session 共享配置很简单,如果配置好了结果不对,不要怀疑自己,肯定是有人在代码里下毒了
  2. 压箱底的东西(debug 源码)虽说不推荐用,但确实是一个万能的方法,不要求你们精通,但必须掌握
  3. 作为一个开发者,一定要有职业素养,开发一半的代码就不要提交了,着实坑人呀!

SpringBoot Session共享,配置不生效问题排查 → 你竟然在代码里下毒!的更多相关文章

  1. CAS 集群部署session共享配置

    背景 前段时间,项目计划搞独立的登录鉴权中心,由于单独开发一套稳定的登录.鉴权代码,工作量大,最终的方案是对开源鉴权中心CAS(Central Authentication Service)作适配修改 ...

  2. limits.conf 配置不生效问题排查

    在部署数据库时,经常会遇到打开最大文件数限制 too many open files 的警告,通常我们只需要修改/etc/security/limits.conf该文件,增加两行,重新登录即可解决. ...

  3. 基于nginx tomcat redis分布式web应用的session共享配置

    一.前言 nginx 作为目前最流行的开源反向代理HTTP Server,用于实现资源缓存.web server负载均衡等功能,由于其轻量级.高性能.高可靠等特点在互联网项目中有着非常普遍的应用,相关 ...

  4. 【nginx】nginx tomcat session 共享配置

    tomcat,redis下载忽略. 一.从github上下载源码,https://github.com/jcoleman/tomcat-redis-session-manager, 将源码复制到开发工 ...

  5. nginx反向代理做负载均衡以及使用redis实现session共享配置详解

    1.为什么要用nginx做负载均衡? 首先我们要知道用单机tomcat做的网站,比较理想的状态下能够承受的并发访问在150到200, 按照并发访问量占总用户数的5%到10%技术,单点tomcat的用户 ...

  6. SpringBoot全局Jackson配置未生效

    在做一个小项目,后台服务第一次用SpringBoot构建.接口使用Json格式,在application.properties中配置如下: spring.jackson.default-propert ...

  7. 从.Net到Java学习第八篇——SpringBoot实现session共享和国际化

    从.Net到Java学习系列目录 SpringBoot Session共享 修改pom.xml添加依赖 <!--spring session--> <dependency> & ...

  8. springboot(十八)-session共享

    前言 在传统的单服务架构中,一般来说,只有一个服务器,那么不存在 Session 共享问题,但是在分布式/集群项目中,Session 共享则是一个必须面对的问题,先看一个简单的架构图: 在这样的架构中 ...

  9. 配置tomcat的session共享

    可通过下面方法限制一个用户访问一个服务器之后就只在该服务器上操作. 请求负载过程中会话信息不能丢失.那么在多个tomcat中session需要共享. 配置tomcat的session共享可以有三种解决 ...

  10. SpringBoot整合NoSql--(四)Session共享

    简介: 正常情况下,HttpSession是通过Servlet 容器创建并进行管理的,创建成功之后都是保存在内存中.如果开发者需要对项目进行横向扩展搭建集群,那么可以利用一些硬件或者软件工具来做负载均 ...

随机推荐

  1. 第二届算法、图像处理与机器视觉国际学术会议(AIPMV2024)

    第二届算法.图像处理与机器视觉国际学术会议(AIPMV2024) 2024 2nd International Conference on Algorithm, Image Processing an ...

  2. Android 耳机驱动知识

    Android 耳机驱动知识 2015-03-06 工作以后接手的第一个驱动就是android平台下耳机的插拔检测和按键检测.这部分涉及的硬件知识比较简单,但是软件上对中断的处理,软件检测的鲁棒性,都 ...

  3. ZYNQ:提取PetaLinux中Linux和UBoot配置、源码

    说明 默认情况下,PetaLinux在编译完成后会删除源代码,以节省硬盘空间. 在project-spec/meta-user/conf/petalinuxbsp.conf里,添加如下内容,可以保留L ...

  4. pyside6 QThread 以及自定义信号 测试

    import sys import random from time import sleep from PySide6 import QtCore as qc from PySide6 import ...

  5. 树莓派4B-GPIO控制步进电机

    树莓派4B-GPIO控制步进电机 硬件需求: 步进电机 树莓派 杜邦线 L298N驱动模块 选择步进电机 首先需要确认步进电机,因为步进电机可分为单极性和双极步进电动机两种,这两种电机的驱动方式是不同 ...

  6. SpringBoot快速插入Mysql 1000万条数据

    导读 有时候为了验证系统瓶颈,需要往数据库表中插入大量数据,可以写sheel脚本插入,前几天为了插入100万条数据,走的sheel脚本(点我直达),插入速度简直无法直视,花了3小时,才插入了10万条, ...

  7. Vite5+Electron聊天室|electron31跨平台仿微信EXE客户端|vue3聊天程序

    基于electron31+vite5+pinia2跨端仿微信Exe聊天应用ViteElectronChat. electron31-vite5-chat原创研发vite5+electron31+pin ...

  8. Mybatis Plus 3.X版本的insert填充自增id的IdType.ID_WORKER策略源码分析

    总结/朱季谦 某天同事突然问我,你知道Mybatis Plus的insert方法,插入数据后自增id是如何自增的吗? 我愣了一下,脑海里只想到,当在POJO类的id设置一个自增策略后,例如@Table ...

  9. 3. set 的使用

    3. set 的使用 因为在实际工程中,我们不会将具体文件全部写出来,这是一件费力不讨好的事情. 3.1 set 定义变量 在 CMake 文件中,默认的变量数据类型是字符串,如果要用别的类型,需要进 ...

  10. [oeasy]python017_万行代码之梦_vim环境_复制粘贴

    继续运行 回忆上次内容 上次 保存运行一条龙 :w|!python3 %   我想 再多输出 几行 增加一下 代码量 可以吗?       添加图片注释,不超过 140 字(可选) 代码量 在正常模式 ...