Spring Boot 中的同一个 Bug,竟然把我坑了两次!
真是郁闷,不过这事又一次提醒我解决问题还是要根治,不能囫囵吞枣,否则相同的问题可能会以不同的形式出现,每次都得花时间去搞。刨根问底,一步到位,再遇到类似问题就可以分分钟解决了。
如果大家没看过松哥之前写的 Spring Boot 整合 Spring Session,可以先回顾下:
第一次踩坑
事情是这样的,大概在今年 6 月初的时候,我在项目中使用到了 Session 共享,当时采用的方案就是 Redis+Spring Session。本来这是一个很简单的问题,我在以前的项目中也用过多次这种方案,早已轻车熟路,但是那次有点不对劲,项目启动时候报了如下错误:

一模一样的代码,但是运行就是会出错,我感觉莫名其妙。因为在 Spring Boot 中整合 Spring Session 是一个非常简单的操作,就几行 Redis 的配置而已,我在确认了代码没问题之后,很快想到了可能是版本问题,因为当时 Spring Boot2.1.5 刚刚发布,我喜欢用最新版。于是我尝试将 Spring Boot 的版本切换到 2.1.4 ,切换回去之后,果然就 OK了,再次启动项目又不会报错了。于是基本确定这是 Spring Boot 的版本升级带来的问题。
但是当时我并没有深究,我以为就是官方出于安全考虑,让你在使用 Redis 时强制加上 Spring Security(因为根据错误提示,很容想到加上 Spring Security 依赖),加上 Spring Security 依赖之后,果然就没有问题了,我也没有多想,这件事就这样过了。
第二次踩坑
前两天我在给星球上的小伙伴录制 Spring Boot 视频的时候,采用了 Spring Boot 最新版 2.1.7,也是 Spring Session,但是在创建项目的时候,忘记添加 Spring Security 依赖了(第一次踩坑之后,我每次用 Spring Session 都会自觉的加上 Spring Security 依赖),运行的时候竟然没报错!我就郁闷了。
于是我去试了 Spring Boot2.1.4、Spring Boot2.1.6 发现都没有问题,在使用 Spring Session 的时候都不需要添加 Spring Security 依赖,只有 Spring Boot2.1.5 才有这个问题。于是我大概明白了,这可能是一个 Bug,而不是版本升级的新功能。
这一次,那我就打算追究一下问题的根源。
源头
要追究问题的源头,我们当然得从 Spring Session 的自动化配置类开始。
在 Spring Boot2.1.5 的 org.springframework.boot.autoconfigure.session.SessionAutoConfiguration 类中,我看到如下源码:
@Bean
@Conditional(DefaultCookieSerializerCondition.class)
public DefaultCookieSerializer cookieSerializer(ServerProperties serverProperties,
ObjectProvider<SpringSessionRememberMeServices> springSessionRememberMeServices) {
//.....
map.from(cookie::getMaxAge).to((maxAge) -> cookieSerializer
.setCookieMaxAge((int) maxAge.getSeconds()));
springSessionRememberMeServices.ifAvailable((
rememberMeServices) -> cookieSerializer.setRememberMeRequestAttribute(
SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR));
return cookieSerializer;
}
从这一段源码中我们可以看到,这里使用到了 SpringSessionRememberMeServices ,而这个类中则用到 Spring Security 中相关的类。因此,如果不引入 Spring Security 就会报错。
我们再来看看 Spring Boot2.1.6 中 org.springframework.boot.autoconfigure.session.SessionAutoConfiguration 类的源码,如下:
@Bean
@Conditional(DefaultCookieSerializerCondition.class)
public DefaultCookieSerializer cookieSerializer(ServerProperties serverProperties) {
//...
map.from(cookie::getMaxAge).to((maxAge) -> cookieSerializer.setCookieMaxAge((int) maxAge.getSeconds()));
if (ClassUtils.isPresent(REMEMBER_ME_SERVICES_CLASS, getClass().getClassLoader())) {
new RememberMeServicesCookieSerializerCustomizer().apply(cookieSerializer);
}
return cookieSerializer;
}
可以看到,在 Spring Boot2.1.6 中,这个问题已经得到修复。这里就没有 2.1.5 那么冲动了,上来了先用 ClassUtils.isPresent 方法判断了下 REMEMBER_ME_SERVICES_CLASS(org.springframework.security.web.authentication.RememberMeServices) 是否存在,存在的话,才有后面的操作。
至此,这个问题就总算弄懂了。
结语
大家平时遇到问题,如果项目不是很赶的话,可以留意多想想,多追究一下原因,说不定你会有很多意外的收获。我这次就是一个活生生的例子,一开始没多想,后来又发现不对劲,前前后后一折腾,反而又多浪费了一些时间。
关注公众号【江南一点雨】,专注于 Spring Boot+微服务以及前后端分离等全栈技术,定期视频教程分享,关注后回复 Java ,领取松哥为你精心准备的 Java 干货!

Spring Boot 中的同一个 Bug,竟然把我坑了两次!的更多相关文章
- spring boot(三):Spring Boot中Redis的使用
spring boot对常用的数据库支持外,对nosql 数据库也进行了封装自动化. redis介绍 Redis是目前业界使用最广泛的内存数据存储.相比memcached,Redis支持更丰富的数据结 ...
- Spring Boot 中的静态资源到底要放在哪里?
当我们使用 SpringMVC 框架时,静态资源会被拦截,需要添加额外配置,之前老有小伙伴在微信上问松哥Spring Boot 中的静态资源加载问题:"松哥,我的HTML页面好像没有样式?& ...
- Spring Boot:Spring Boot 中 Redis 的使用
Redis 介绍 Redis 是目前业界使用最广泛的内存数据存储.相比 Memcached,Redis 支持更丰富的数据结构,例如 hashes, lists, sets 等,同时支持数据持久化.除此 ...
- (转)Spring Boot(三):Spring Boot 中 Redis 的使用
http://www.ityouknow.com/springboot/2016/03/06/spring-boot-redis.html Spring Boot 对常用的数据库支持外,对 Nosql ...
- Spring Boot 中配置文件application.properties使用
一.配置文档配置项的调用(application.properties可放在resources,或者resources下的config文件夹里) package com.my.study.contro ...
- 学习Spring Boot:(二十三)Spring Boot 中使用 Docker
前言 简单的学习下怎么在 Spring Boot 中使用 Docker 进行构建,发布一个镜像,现在我们通过远程的 docker api 构建镜像,运行容器,发布镜像等操作. 这里只介绍两种方式: 远 ...
- Spring Boot(三):Spring Boot 中 Redis 的使用
Spring Boot 对常用的数据库支持外,对 Nosql 数据库也进行了封装自动化. Redis 介绍 Redis 是目前业界使用最广泛的内存数据存储.相比 Memcached,Redis 支持更 ...
- Spring Boot中使用EhCache实现缓存支持
SpringBoot提供数据缓存功能的支持,提供了一系列的自动化配置,使我们可以非常方便的使用缓存.,相信非常多人已经用过cache了.因为数据库的IO瓶颈.一般情况下我们都会引入非常多的缓存策略, ...
- Spring Boot中实现logback多环境日志配置
在Spring Boot中,可以在logback.xml中的springProfile标签中定义多个环境logback.xml: <springProfile name="produc ...
随机推荐
- 嵊州D2T2 八月惊魂 全排列 next_permutation()
嵊州D2T2 八月惊魂 这是一个远古时期的秘密,至今已无人关心. 这个世界的每个时代可以和一个 1 ∼ n 的排列一一对应. 时代越早,所对应的排列字典序就越小. 我们知道,公爵已经是 m 个时代前的 ...
- WPF 入门笔记之布局
一.布局原则: 1. 不应显示的设定元素的尺寸,反而元素可以改变它的尺寸,并适应它们的内容 2. 不应使用平布的坐标,指定元素的位置. 3. 布局容器和它的子元素是共享可以使用的空间 4. 可以嵌套的 ...
- bzoj3316 JC loves Mkk题解
3316: JC loves Mkk Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 979 Solved: 316[Submit][Status][Di ...
- 快速掌握mongoDB(二)——聚合管道和MapReduce
上一节简单介绍了一下mongoDB的增删改查操作,这一节将介绍其聚合操作.我们在使用mysql.sqlserver时经常会用到一些聚合函数,如sum/avg/max/min/count等,mongoD ...
- 【二分讲解及例题】火车站台连锁店-C++
首先我们先来从一个小游戏理解一下二分.(摘自程序员小灰的博客) 为什么说这样效率最高呢?因为每一次选择数字,无论偏大还是偏小,都可以让剩下的选择范围缩小一半. 给定范围0到1000的整数: 第一次我们 ...
- 描述符\get/set/delete,init/new/call,元类
6.23 自我总结 1.描述符__get__,__set__,__delete__ 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了____get__(),__set__(),__d ...
- [PTA] 数据结构与算法题目集 6-10 二分查找
Position BinarySearch(List L, ElementType X) { int beg = 1; int end = L->Last; while (beg <= e ...
- [leetcode] 134. Gas Station (medium)
原题 题意: 过一个循环的加油站,每个加油站可以加一定数量的油,走到下一个加油站需要消耗一定数量的油,判断能否走一圈. 思路: 一开始思路就是遍历一圈,最直接的思路. class Solution { ...
- python基础练习 斐波那契数列
转载于知乎刘奕聪的方法 一 f = [1, 1]print([f.append((f[-1] + f[-2])) or f.pop(0) for i in range(100)]) /// f.ap ...
- .NET 欢乐编程术之类型超级转换之术👍👍
准备工作:先确保 VS 版本大于 2017,且支持C# 7.0 语言版本.然后新建 .Net Core 项目,在 Nuget 包管理上引入微软霸霸官方包 System.Runtime.Compiler ...