真是郁悶,不過這事又一次提醒我解決問題還是要根治,不能囫圇吞棗,否則相同的問題可能會(huì)以不同的形式出現(xiàn),每次都得花時(shí)間去搞。刨根問底,一步到位,再遇到類似問題就可以分分鐘解決了。
如果大家沒看過松哥之前寫的 Spring Boot 整合 Spring Session,可以先回顧下:
* Spring Boot 一個(gè)依賴搞定 session 共享,沒有比這更簡(jiǎn)單的方案了!
<https://mp.weixin.qq.com/s/xs67SzSkMLz6-HgZVxTDFw>
第一次踩坑
事情是這樣的,大概在今年 6 月初的時(shí)候,我在項(xiàng)目中使用到了 Session 共享,當(dāng)時(shí)采用的方案就是 Redis+Spring
Session。本來這是一個(gè)很簡(jiǎn)單的問題,我在以前的項(xiàng)目中也用過多次這種方案,早已輕車熟路,但是那次有點(diǎn)不對(duì)勁,項(xiàng)目啟動(dòng)時(shí)候報(bào)了如下錯(cuò)誤:
一模一樣的代碼,但是運(yùn)行就是會(huì)出錯(cuò),我感覺莫名其妙。因?yàn)樵?Spring Boot 中整合 Spring Session 是一個(gè)非常簡(jiǎn)單的操作,就幾行
Redis 的配置而已,我在確認(rèn)了代碼沒問題之后,很快想到了可能是版本問題,因?yàn)楫?dāng)時(shí) Spring Boot2.1.5 剛剛發(fā)布,我喜歡用最新版。于是我嘗試將
Spring Boot 的版本切換到 2.1.4 ,切換回去之后,果然就 OK了,再次啟動(dòng)項(xiàng)目又不會(huì)報(bào)錯(cuò)了。于是基本確定這是 Spring Boot
的版本升級(jí)帶來的問題。
但是當(dāng)時(shí)我并沒有深究,我以為就是官方出于安全考慮,讓你在使用 Redis 時(shí)強(qiáng)制加上 Spring Security(因?yàn)楦鶕?jù)錯(cuò)誤提示,很容想到加上
Spring Security 依賴),加上 Spring Security 依賴之后,果然就沒有問題了,我也沒有多想,這件事就這樣過了。?
第二次踩坑
前兩天我在給星球上的小伙伴錄制 Spring Boot 視頻的時(shí)候,采用了 Spring Boot 最新版 2.1.7,也是 Spring
Session,但是在創(chuàng)建項(xiàng)目的時(shí)候,忘記添加 Spring Security 依賴了(第一次踩坑之后,我每次用 Spring Session 都會(huì)自覺的加上
Spring Security 依賴),運(yùn)行的時(shí)候竟然沒報(bào)錯(cuò)!我就郁悶了。
于是我去試了 Spring Boot2.1.4、Spring Boot2.1.6 發(fā)現(xiàn)都沒有問題,在使用 Spring Session 的時(shí)候都不需要添加
Spring Security 依賴,只有 Spring Boot2.1.5 才有這個(gè)問題。于是我大概明白了,這可能是一個(gè) Bug,而不是版本升級(jí)的新功能。
這一次,那我就打算追究一下問題的根源。
源頭
要追究問題的源頭,我們當(dāng)然得從 Spring Session 的自動(dòng)化配置類開始。
在 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 ,而這個(gè)類中則用到 Spring Security
中相關(guān)的類。因此,如果不引入 Spring Security 就會(huì)報(bào)錯(cuò)。
我們?cè)賮砜纯?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 中,這個(gè)問題已經(jīng)得到修復(fù)。這里就沒有 2.1.5 那么沖動(dòng)了,上來了先用
ClassUtils.isPresent 方法判斷了下 REMEMBER_ME_SERVICES_CLASS(
org.springframework.security.web.authentication.RememberMeServices)
是否存在,存在的話,才有后面的操作。
至此,這個(gè)問題就總算弄懂了。
結(jié)語(yǔ)
大家平時(shí)遇到問題,如果項(xiàng)目不是很趕的話,可以留意多想想,多追究一下原因,說不定你會(huì)有很多意外的收獲。我這次就是一個(gè)活生生的例子,一開始沒多想,后來又發(fā)現(xiàn)不對(duì)勁,前前后后一折騰,反而又多浪費(fèi)了一些時(shí)間。
關(guān)注公眾號(hào)【江南一點(diǎn)雨】,專注于 Spring Boot+微服務(wù)以及前后端分離等全棧技術(shù),定期視頻教程分享,關(guān)注后回復(fù) Java ,領(lǐng)取松哥為你精心準(zhǔn)備的
Java 干貨!
熱門工具 換一換