SpringBoot Session共享,配置不生效问题排查 → 你竟然在代码里下毒!
开心一刻
快 8 点了,街边卖油条的还没来,我只能给他打电话
大哥在电话中说到:劳资卖了这么多年油条,从来都是自由自在,自从特么认识了你,居然让我有了上班的感觉!
Session 共享
SpringBoot
session 共享配置,我相信你们都会,但出于负责的态度,我还是给你们演示一遍
添加依赖
<?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>
添加配置
文件配置
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
操作 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") + "";
}
}
至此,搭建就算完成了,启动后访问
然后去 redis 看 session 信息
有效时长为什么是 870
而不是 900
,请把头伸过来,我悄悄告诉你
我就问你们,SpringBoot Session
共享是不是很简单?但就是这么简单的内容,竟然有人往里面下毒,而我很不幸的成了那个中毒之人,如果不是我有绝招,说不定就噶过去了,具体细节且听我慢慢道来
配置不生效
实际项目中,我也是按如上配置的,可 redis 中的存放内容却是
从结果来看,session 确实是共享了,但为什么 maxInactiveIntervalInSeconds
、redisNamespace
配置都未生效?我还特意去对比了另外一个项目,一样的配置流程,那个项目的 命名空间
和 有效时长
都是正常生效的,而此项目却未生效,这就让我彻底懵圈了
debug 源码
该尝试的都尝试了,maxInactiveIntervalInSeconds
、redisNamespace
始终不生效,没有办法了,只能上绝招了
debug 调试源码
问题又来了:断点打在哪?有两个地方需要打断点
RedisHttpSessionConfiguration#sessionRepository
跟进到
@EnableRedisHttpSession
注解里面,会看到@Import(RedisHttpSessionConfiguration.class)
,跟进RedisHttpSessionConfiguration
,会看到被@Bean
修饰的sessionRepository
方法,正常情况下,SpringBoot
启动过程中会调用该方法,我们在该方法第一行打个断点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
后重启,一切恢复正常,maxInactiveIntervalInSeconds
、redisNamespace
都正常生效;实际工作开发中,此事就完结了,不要再去细扣了,除非你确实闲的蛋疼。但话说回来,你们都来看博客了,那确实是闲,既然你们这么闲,那我们继续扣一扣,扣什么呢
为什么我们指定 RedisOperationsSessionRepository 后,RedisHttpSessionConfiguration#sessionRepository 方法不被调用,而且 maxInactiveIntervalInSeconds 、redisNamespace 不生效
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 实例maxInactiveIntervalInSeconds 、redisNamespace 为什么不生效
大家注意看
RedisConfig
@Configuration
public class RedisConfig { @Autowired
private RedisTemplate redisTemplate; @Bean({"redisOperationsSessionRepository", "sessionRepository"})
public RedisOperationsSessionRepository redisOperationsSessionRepository() {
return new RedisOperationsSessionRepository(redisTemplate);
}
}
试问如何让 maxInactiveIntervalInSeconds 、redisNamespace 生效?
既然官方已经把
RedisOperationsSessionRepository
废弃了,我们就不要纠结它了,直接不用它!
总结
- SpringBoot Session 共享配置很简单,如果配置好了结果不对,不要怀疑自己,肯定是有人在代码里下毒了
- 压箱底的东西(debug 源码)虽说不推荐用,但确实是一个万能的方法,不要求你们精通,但必须掌握
- 作为一个开发者,一定要有职业素养,开发一半的代码就不要提交了,着实坑人呀!
SpringBoot Session共享,配置不生效问题排查 → 你竟然在代码里下毒!的更多相关文章
- CAS 集群部署session共享配置
背景 前段时间,项目计划搞独立的登录鉴权中心,由于单独开发一套稳定的登录.鉴权代码,工作量大,最终的方案是对开源鉴权中心CAS(Central Authentication Service)作适配修改 ...
- limits.conf 配置不生效问题排查
在部署数据库时,经常会遇到打开最大文件数限制 too many open files 的警告,通常我们只需要修改/etc/security/limits.conf该文件,增加两行,重新登录即可解决. ...
- 基于nginx tomcat redis分布式web应用的session共享配置
一.前言 nginx 作为目前最流行的开源反向代理HTTP Server,用于实现资源缓存.web server负载均衡等功能,由于其轻量级.高性能.高可靠等特点在互联网项目中有着非常普遍的应用,相关 ...
- 【nginx】nginx tomcat session 共享配置
tomcat,redis下载忽略. 一.从github上下载源码,https://github.com/jcoleman/tomcat-redis-session-manager, 将源码复制到开发工 ...
- nginx反向代理做负载均衡以及使用redis实现session共享配置详解
1.为什么要用nginx做负载均衡? 首先我们要知道用单机tomcat做的网站,比较理想的状态下能够承受的并发访问在150到200, 按照并发访问量占总用户数的5%到10%技术,单点tomcat的用户 ...
- SpringBoot全局Jackson配置未生效
在做一个小项目,后台服务第一次用SpringBoot构建.接口使用Json格式,在application.properties中配置如下: spring.jackson.default-propert ...
- 从.Net到Java学习第八篇——SpringBoot实现session共享和国际化
从.Net到Java学习系列目录 SpringBoot Session共享 修改pom.xml添加依赖 <!--spring session--> <dependency> & ...
- springboot(十八)-session共享
前言 在传统的单服务架构中,一般来说,只有一个服务器,那么不存在 Session 共享问题,但是在分布式/集群项目中,Session 共享则是一个必须面对的问题,先看一个简单的架构图: 在这样的架构中 ...
- 配置tomcat的session共享
可通过下面方法限制一个用户访问一个服务器之后就只在该服务器上操作. 请求负载过程中会话信息不能丢失.那么在多个tomcat中session需要共享. 配置tomcat的session共享可以有三种解决 ...
- SpringBoot整合NoSql--(四)Session共享
简介: 正常情况下,HttpSession是通过Servlet 容器创建并进行管理的,创建成功之后都是保存在内存中.如果开发者需要对项目进行横向扩展搭建集群,那么可以利用一些硬件或者软件工具来做负载均 ...
随机推荐
- xshell和xftp远程SSH密钥连接腾讯云服务器
1.在腾讯云中创建密钥,关机,绑定. 2.xshell和xftp导入密钥,填写的用户名和密码验证是 腾讯云的主机的root和密码(关键,而不是创建密钥的用户名和密码),该密码可以重置.
- Apollo quick start SampleApp demo Java
<!--配置中心--> <dependency> <groupId>com.ctrip.framework.apollo</groupId> <a ...
- nomp矿池源码详解
1 项目简介 Node Open Mining Portal(简称NOMP)是一个由Node.js编写的高效.可扩展的加密货币挖矿池软件,专为经验丰富的系统管理员和开发者设计.它包含了Stratum挖 ...
- Java跳动爱心代码
1.计算爱心曲线上的点的公式 计算爱心曲线上的点的公式通常基于参数方程.以下是两种常见的参数方程表示方法,用于绘制爱心曲线: 1.1基于 (x, y) 坐标的参数方程 x = a * (2 * cos ...
- Js 实现导航li列表,选中时,显示选中样式
结合Django项目实现 实现步骤: html页面部分,使用bootstrap.css中的样式(不用可忽略,主要class样式),要引用bootstrap.css,使用到actvie样式: <l ...
- EIGRP总结
EIGRP 思科私有,2013年公开,其他厂商不支持,所以用得不是很多 几秒钟就能完成收敛 触发更新,只要网络不发生变化就不会发生更新 按需更新,只更新变化的部分 ...
- 01-前端开发Vscode插件配置
01 自动保存配置 02 空格渲染方式 配置好以后,可以看到代码的空格有几个,以点的方式呈现,1个点表示1个空格 03 图标插件 VSCode Great Icons 04 缩进 推荐使用2 05 v ...
- 关于ZYNQ-7000中断调试一点感想
背景 在ZYNQ 平台下,需要对各种需要的底层接口进行初始化. 我依次调试了很多驱动,从最简单的网口到USB:再到读写PL端的寄存器(通过AXI总线,内存映射读写物理地址实现),到中断的时候一直卡着不 ...
- Freertos学习:04-任务的调试函数
--- title: rtos-freertos-04-任务的调试函数 EntryName: rtos-freertos-04-task-debug date: 2020-06-22 08:49:06 ...
- TI AM64x工业开发板硬件说明书(双核ARM Cortex-A53 + 单/四核Cortex-R5F + 单核Cortex-M4F,主频1GHz)
前 言 本文主要介绍TL64x-EVM评估板硬件接口资源以及设计注意事项等内容. 创龙科技TL64x-EVM是一款基于TI Sitara系列AM64x双核ARM Cortex-A53 + 单/四核C ...