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