Spring Session 官方文檔的描述是非常簡單的, 很多東西講的並不是很清楚, 也可能是因爲Spring 官方覺得Spring session太簡單, 沒什麼值得多介紹吧. 目前網上關於Spring session的教程並沒有太多, 所以筆者決定嘗試從源碼角度對spring session 的核心步驟做一下簡單的分析.
1. Spring Session 配置初始化分析
1.1 @EnableRedisHttpSession 分析
SpringBoot 整合大部分框架都是通過@Enablexxx作爲框架的入口的, 因此筆者也嘗試從@EnableRedisHttpSession 配置類開始分析:
- @EnableRedisHttpSession 註解定義時, 使用@Configuration 修飾, 說明是@EnableRedisHttpSession修飾的類, 會被Spring 當做配置類進行解析
- @EnableRedisHttpSession 中使用@Import 註解導入了RedisHttpSessionConfiguration, 那麼在應用啓動時會向spring 容器中注入RedisHttpSessionConfiguration 實例
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(RedisHttpSessionConfiguration.class)
@Configuration
public @interface EnableRedisHttpSession {
int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
String redisNamespace() default RedisOperationsSessionRepository.DEFAULT_NAMESPACE;
RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;
String cleanupCron() default RedisHttpSessionConfiguration.DEFAULT_CLEANUP_CRON;
}
1.2 RedisHttpSessionConfiguration 分析
- RedisHttpSessionConfiguration 本身是一個Spring 配置類, 會向spring 容器註冊sessionRepository, redisMessageListenerContainer 等實例
- RedisHttpSessionConfiguration 會註冊Redis 消息監聽器容器RedisMessageListenerContainer, 並將RedisOperationsSessionRepository 作爲Redis 消息訂閱的監聽器, 因爲它實現了MessageListener接口. 當Redis 中key 過期或銷燬時, 會通知將RedisOperationsSessionRepository 調用其onMessage 方法來處理消息.
- RedisHttpSessionConfiguration 繼承了RedisHttpSessionConfiguration, 所以會繼承RedisHttpSessionConfiguration中的公有方法
@Configuration
@EnableScheduling
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware,
SchedulingConfigurer {
// 省略部分代碼....
// 初始化Spring session 配置信息, 即解析@EnableRedisHttpSession 註解的參數配置信息
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> attributeMap = importMetadata
.getAnnotationAttributes(EnableRedisHttpSession.class.getName());
AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap);
this.maxInactiveIntervalInSeconds = attributes
.getNumber("maxInactiveIntervalInSeconds");
String redisNamespaceValue = attributes.getString("redisNamespace");
if (StringUtils.hasText(redisNamespaceValue)) {
this.redisNamespace = this.embeddedValueResolver
.resolveStringValue(redisNamespaceValue);
}
this.redisFlushMode = attributes.getEnum("redisFlushMode");
String cleanupCron = attributes.getString("cleanupCron");
if (StringUtils.hasText(cleanupCron)) {
this.cleanupCron = cleanupCron;
}
}
// 利用Redis 發佈訂閱機制, 訂閱消息的
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(this.redisConnectionFactory);
if (this.redisTaskExecutor != null) {
container.setTaskExecutor(this.redisTaskExecutor);
}
if (this.redisSubscriptionExecutor != null) {
container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
}
// 訂閱監聽消息銷燬/過期
container.addMessageListener(sessionRepository(), Arrays.asList(
new ChannelTopic(sessionRepository().getSessionDeletedChannel()),
new ChannelTopic(sessionRepository().getSessionExpiredChannel())));
// 訂閱消息創建
container.addMessageListener(sessionRepository(),
Collections.singletonList(new PatternTopic(
sessionRepository().getSessionCreatedChannelPrefix() + "*")));
return container;
}
}
1.3 RedisHttpSessionConfiguration 分析
- SpringHttpSessionConfiguration 會將自定義的所有SessionListener 封裝爲一個SessionEventHttpSessionListenerAdapter
- SpringHttpSessionConfiguration 會初始化一個最核心的組件SessionRepositoryFilter, 該過濾器會攔截所有的http 請求, 解析並處理session.
@Configuration
public class SpringHttpSessionConfiguration implements ApplicationContextAware {
// 省略部分代碼
// 註冊SessionListener
@Bean
public SessionEventHttpSessionListenerAdapter sessionEventHttpSessionListenerAdapter() {
return new SessionEventHttpSessionListenerAdapter(this.httpSessionListeners);
}
// 向spring 容器注入核心的過濾器
@Bean
public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(
SessionRepository<S> sessionRepository) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(
sessionRepository);
sessionRepositoryFilter.setServletContext(this.servletContext);
sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
return sessionRepositoryFilter;
}
}