真是郁悶,不過這事又一次提醒我解決問題還是要根治,不能囫圇吞棗,否則相同的問題可能會以不同的形式出現,每次都得花時間去搞。刨根問底,一步到位,再遇到類似問題就可以分分鍾解決了。
如果大家沒看過松哥之前寫的 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 干貨!