Spring-Session改造總結

Spring-Session改造總結

前言

當使用集羣方式部署WEB服務時,訪問請求可能會發到集羣中的任何一臺機器,如何在多臺機器間共享session信息就會成爲一個問題,而spring-session是個很好的解決方案。
spring-session以spring-session-core爲核心,實現了以jdbc(也包括一些常見的數據庫)爲存儲方案的spring-session-jdbc,以及以redis爲存儲方案的spring-session-data-redis,還有一個hazelcast(這個沒玩過)。
個人認爲redis、jdbc方案是能滿足大部分的項目需求的,偏偏我們做項目需要使用的數據存儲不在spring-session支持的列表範圍內,只能參考redis和jdbc方案重寫一份了,這裏簡要總結一下開發過程。

關鍵點1: 定義自己的SessionRepository

這個就是session管理的核心類了,session數據的操作都在該類實現。spring-session-core定義了FindByIndexNameSessionRepository接口,而該接口繼承自 SessionRepositoy。

public interface SessionRepository<S extends Session> {
    S createSession();
    void save(S session);
    S findById(String id);
    void deleteById(String id);
}

從SessionRepositoy接口的定義可以看出,該接口就是純粹的增刪改查,不意外。

public interface FindByIndexNameSessionRepository<S extends Session>
        extends SessionRepository<S> {  
    String PRINCIPAL_NAME_INDEX_NAME = FindByIndexNameSessionRepository.class.getName()
            .concat(".PRINCIPAL_NAME_INDEX_NAME");
    /**
    * Find a Map of the session id to the {@link Session} of all sessions that contain
    * the session attribute with the name
    * {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME} and the value of
    * the specified principal name.
    *
    */
    Map<String, S> findByIndexNameAndIndexValue(String indexName, String indexValue);
}

而FindByIndexNameSessionRepository只是多定義了一個接口,看註釋可能也會比較迷糊(熟悉spring-security的可能會根據principal猜到),一句話解釋該接口的作用就是根據用戶名反查該用戶在線session集合,這個功能對於WEB應用的後臺管理必不可少,查看哪些用戶在線以及需要強制下線的時候,都需要用到該接口。 當然,該接口定義了兩個參數,而不是單參數(用戶名),應該有功能抽象和擴展性方面的考慮,只是目前不需要而已。redis和jdbc的函數實現都有一個判斷可以看出該函數功能的單一:

if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
    return Collections.emptyMap();
}

實現FindByIndexNameSessionRepository接口就是實現了對數據源操作的適配,這個是改造時必不可少的工作。

關鍵點2: 定義sessionRepository bean

該bean定義後,Spring就可以使用自定義SessionRepository進行session的管理。 在jdbc和redis中,該定義都放在config.annotation.web.http的Configuration中:

JdbcHttpSessionConfiguration:
@Bean
public JdbcOperationsSessionRepository sessionRepository() {
    ...
};

RedisHttpSessionConfiguration:
@Bean
public RedisOperationsSessionRepository sessionRepository() {
    ...
};

當然讓Spring使用自定義SessionRepository這個過程是間接的,是通過spring-session-core中的SessionRepositoryFilter實現,SessionRepositoryFilter處於filter調用鏈的前端,通過各種wrapper封裝,將對session的各種操作最終都調用到自定義SessionRepository中。

關鍵點3: 定義sessionRegistry bean

我們不需要自己實現SessionRegistry接口,spring-session-core中已經定義了默認的實現SpringSessionBackedSessionRegistry,但是我們需要定義sessionRegistry bean以及設置。
(這個定義在jdbc和redis項目中並沒有體現,可能是因爲該bean與spring-security的設置有關,不應該在spring-session中預設,而spring-session提供默認實現,只是方便開發人員方便使用而已。)

具體使用類似:

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private FindByIndexNameSessionRepository<Session> sessionRepository;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            // other config goes here...
            .sessionManagement()
                .maximumSessions(2)
                .sessionRegistry(sessionRegistry());
        // @formatter:on
    }

    @Bean
    SpringSessionBackedSessionRegistry sessionRegistry() {
        return new SpringSessionBackedSessionRegistry<>(this.sessionRepository);
    }
}

可以看出sessionRegistry主要是爲了與spring-security融合,需要注意的是,如果使用了自定義的SessionRepository並且需要進行Session的併發管理(調用了maximumSessions),則HttpSecurity的sessionRegistry必須要設置,否則spring-security會使用默認的SessionRegistry實現來進行session管理, 默認實現當然是不支持集羣了。

結束語

定製一個spring-session,只需要注意以上三點,就可以使之正常運行。

參考資料:
[1] spring-session
[2] spring-session source code

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章