Spring Boot 中的同一個 Bug,竟然把我坑了兩次!

江南一點雨發表於2019-08-14

真是鬱悶,不過這事又一次提醒我解決問題還是要根治,不能囫圇吞棗,否則相同的問題可能會以不同的形式出現,每次都得花時間去搞。刨根問底,一步到位,再遇到類似問題就可以分分鐘解決了。

如果大家沒看過鬆哥之前寫的 Spring Boot 整合 Spring Session,可以先回顧下:

第一次踩坑

事情是這樣的,大概在今年 6 月初的時候,我在專案中使用到了 Session 共享,當時採用的方案就是 Redis+Spring Session。本來這是一個很簡單的問題,我在以前的專案中也用過多次這種方案,早已輕車熟路,但是那次有點不對勁,專案啟動時候報瞭如下錯誤:

Spring Boot 中的同一個 Bug,竟然把我坑了兩次!

一模一樣的程式碼,但是執行就是會出錯,我感覺莫名其妙。因為在 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,竟然把我坑了兩次!

相關文章