概述
本文基于以下组合的应用,通过源代码分析一下一个Spring Boot应用中Spring Session的配置过程:
Spring Boot 2.1.3.RELEASESpring Session Core 2.1.4.RELEASESpring Session Data Redis 2.1.3.RELEASESpring Web MVC 5.1.5.RELEASE
在一个Spring Boot应用中,关于Spring Session的配置,首先要提到的就是自动配置类SessionAutoConfiguration了。
源代码分析
源代码 SessionAutoConfiguration
package org.springframework.boot.autoconfigure.session;
// 省略 imports 行
/**
* EnableAutoConfiguration Auto-configuration for Spring Session.
*
* @since 1.4.0
*/
// 声明这是一个配置类
@Configuration
// 仅在类 Session 存在于 classpath 时候才生效,
// Session 类由包 Spring Session Core 提供
@ConditionalOnClass(Session.class)
// 仅在当前应用是 Web 应用时才生效 : Servlet Web 应用, Reactive Web 应用都可以
@ConditionalOnWebApplication
// 确保如下前缀的配置属性的加载到如下 bean :
// server ==> ServerProperties
// spring.session ==> SessionProperties
@EnableConfigurationProperties({ ServerProperties.class, SessionProperties.class })
// 当前配置必须在指定的自动配置结束之后进行,这里虽然列出了很多,但同一应用中它们未必
// 都存在,这里指的是当前应用中如果它们中间某些存在的话,SessionAutoConfiguration
// 自动配置的执行必须要在这些自动配置结束之后完成,本文的分析使用 Redis 支持 Spring Session,
// 并且是 Servlet Web 应用,所以 RedisAutoConfiguration 会被启用
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HazelcastAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoReactiveDataAutoConfiguration.class, RedisAutoConfiguration.class,
RedisReactiveAutoConfiguration.class })
// 在自动配置 HttpHandlerAutoConfiguration 执行前执行
@AutoConfigureBefore(HttpHandlerAutoConfiguration.class)
public class SessionAutoConfiguration {
// 内嵌配置子类,针对 Servlet Web 的情况
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
// 1. 导入 ServletSessionRepositoryValidator,确保存储库类型被指定以及相应的存储库类的存在;
// 2. 导入 SessionRepositoryFilterConfiguration, 配置注册SessionRepositoryFilter到Servlet容器的
// FilterRegistrationBean
@Import({ ServletSessionRepositoryValidator.class,
SessionRepositoryFilterConfiguration.class })
static class ServletSessionConfiguration {
// 定义一个 bean cookieSerializer
@Bean
// 仅在条件 DefaultCookieSerializerCondition 被满足时才生效
@Conditional(DefaultCookieSerializerCondition.class)
public DefaultCookieSerializer cookieSerializer(
ServerProperties serverProperties) {
Cookie cookie = serverProperties.getServlet().getSession().getCookie();
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(cookie::getName).to(cookieSerializer::setCookieName);
map.from(cookie::getDomain).to(cookieSerializer::setDomainName);
map.from(cookie::getPath).to(cookieSerializer::setCookiePath);
map.from(cookie::getHttpOnly).to(cookieSerializer::setUseHttpOnlyCookie);
map.from(cookie::getSecure).to(cookieSerializer::setUseSecureCookie);
map.from(cookie::getMaxAge).to((maxAge) -> cookieSerializer
.setCookieMaxAge((int) maxAge.getSeconds()));
return cookieSerializer;
}
// 内嵌配置类
// 该类自身没有提供任何实现,其效果主要通过注解来实现 :
// 仅在 bean SessionRepository 不存在时导入 ServletSessionRepositoryImplementationValidator
// 和 ServletSessionConfigurationImportSelector
@Configuration
// 仅在 bean SessionRepository 不存在时生效
@ConditionalOnMissingBean(SessionRepository.class)
// 导入 ServletSessionRepositoryImplementationValidator
// 和 ServletSessionConfigurationImportSelector
@Import({ ServletSessionRepositoryImplementationValidator.class,
ServletSessionConfigurationImportSelector.class })
static class ServletSessionRepositoryConfiguration {
}
}
// 内嵌配置子类,针对 Reactive Web 的情况
@Configuration
@ConditionalOnWebApplication(type = Type.REACTIVE)
@Import(ReactiveSessionRepositoryValidator.class)
static class ReactiveSessionConfiguration {
// 内嵌配置类
// 该类自身没有提供任何实现,其效果主要通过注解来实现 :
// 仅在 bean ReactiveSessionRepository 不存在时导入 ReactiveSessionRepositoryImplementationValidator
// 和 ReactiveSessionConfigurationImportSelector
@Configuration
// 仅在 bean ReactiveSessionRepository 不存在时生效
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
// 导入 ReactiveSessionRepositoryImplementationValidator
// 和 ReactiveSessionConfigurationImportSelector
@Import({ ReactiveSessionRepositoryImplementationValidator.class,
ReactiveSessionConfigurationImportSelector.class })
static class ReactiveSessionRepositoryConfiguration {
}
}
/**
* Condition to trigger the creation of a DefaultCookieSerializer. This kicks
* in if either no HttpSessionIdResolver and CookieSerializer beans
* are registered, or if CookieHttpSessionIdResolver is registered but
* CookieSerializer is not.
* 触发创建 DefaultCookieSerializer 的条件 :
* 1. Bean HttpSessionIdResolver 和 CookieSerializer 都不存在 或者
* 2. Bean CookieHttpSessionIdResolver 存在 但 bean CookieSerializer 不存在
*
* DefaultCookieSerializerCondition 是一个 AnyNestedCondition,
* 这种条件被满足的条件是 : 某个内嵌子条件被满足
*/
static class DefaultCookieSerializerCondition extends AnyNestedCondition {
DefaultCookieSerializerCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnMissingBean({ HttpSessionIdResolver.class, CookieSerializer.class })
static class NoComponentsAvailable {
}
@ConditionalOnBean(CookieHttpSessionIdResolver.class)
@ConditionalOnMissingBean(CookieSerializer.class)
static class CookieHttpSessionIdResolverAvailable {
}
}
/**
* ImportSelector base class to add StoreType configuration classes.
* 抽象基类,提供工具方法用于不同 Web 环境下决定导入哪些 Session Store 配置类
*/
abstract static class SessionConfigurationImportSelector implements ImportSelector {
protected final String[] selectImports(WebApplicationType webApplicationType) {
List<String> imports = new ArrayList<>();
StoreType[] types = StoreType.values();
for (int i = 0; i < types.length; i++) {
imports.add(SessionStoreMappings.getConfigurationClass(webApplicationType,
types[i]));
}
return StringUtils.toStringArray(imports);
}
}
/**
* ImportSelector to add StoreType configuration classes for reactive
* web applications.
* 在 Reactive Web 情况下使用,用于导入相应的 Session Store 配置类
*/
static class ReactiveSessionConfigurationImportSelector
extends SessionConfigurationImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return super.selectImports(WebApplicationType.REACTIVE);
}
}
/**
* ImportSelector to add StoreType configuration classes for Servlet
* web applications.
* 在 Servlet Web 情况下使用,用于导入相应的 Session Store 配置类
*/
static class ServletSessionConfigurationImportSelector
extends SessionConfigurationImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return super.selectImports(WebApplicationType.SERVLET);
}
}
/**
* Base class for beans used to validate that only one supported implementation is
* available in the classpath when the store-type property is not set.
* 抽象基类,用于检查 store type 未设置的情况下仅有一个session repository 实现类存在于 classpath
*/
abstract static class AbstractSessionRepositoryImplementationValidator {
private final List<String> candidates;
private final ClassLoader classLoader;
private final SessionProperties sessionProperties;
AbstractSessionRepositoryImplementationValidator(
ApplicationContext applicationContext,
SessionProperties sessionProperties, List<String> candidates) {
this.classLoader = applicationContext.getClassLoader();
this.sessionProperties = sessionProperties;
this.candidates = candidates;
}
@PostConstruct
public void checkAvailableImplementations() {
List<Class<?>> availableCandidates = new ArrayList<>();
for (String candidate : this.candidates) {
addCandidateIfAvailable(availableCandidates, candidate);
}
StoreType storeType = this.sessionProperties.getStoreType();
if (availableCandidates.size() > 1 && storeType == null) {
// 这里通过异常方式确保storeType 属性未设置时必须只有一个session存储库实现类存在
throw new NonUniqueSessionRepositoryException(availableCandidates);
}
}
// 对类型 type 进行检查,如果该类型对应的类能够被 classLoader 加载成功,则将其作为候选类,
// 也就是添加到列表 candidates 中,否则该类型 type 不作为候选。
private void addCandidateIfAvailable(List<Class<?>> candidates, String type) {
try {
Class<?> candidate = this.classLoader.loadClass(type);
if (candidate != null) {
candidates.add(candidate);
}
}
catch (Throwable ex) {
// Ignore
}
}
}
/**
* Bean used to validate that only one supported implementation is available in the
* classpath when the store-type property is not set.
*/
static class ServletSessionRepositoryImplementationValidator
extends AbstractSessionRepositoryImplementationValidator {
ServletSessionRepositoryImplementationValidator(
ApplicationContext applicationContext,
SessionProperties sessionProperties) {
super(applicationContext, sessionProperties, Arrays.asList(
"org.springframework.session.hazelcast.HazelcastSessionRepository",
"org.springframework.session.jdbc.JdbcOperationsSessionRepository",
"org.springframework.session.data.mongo.MongoOperationsSessionRepository",
"org.springframework.session.data.redis.RedisOperationsSessionRepository"));
}
}
/**
* Bean used to validate that only one supported implementation is available in the
* classpath when the store-type property is not set.
*/
static class ReactiveSessionRepositoryImplementationValidator
extends AbstractSessionRepositoryImplementationValidator {
ReactiveSessionRepositoryImplementationValidator(
ApplicationContext applicationContext,
SessionProperties sessionProperties) {
super(applicationContext, sessionProperties, Arrays.asList(
"org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository",
"org.springframework.session.data.mongo.ReactiveMongoOperationsSessionRepository"));
}
}
/**
* Base class for validating that a (reactive) session repository bean exists.
* 抽象基类,用于确保只有一个 session repository bean 实例存在,如果有多个,则抛出异常
*/
abstract static class AbstractSessionRepositoryValidator {
private final SessionProperties sessionProperties;
private final ObjectProvider<?> sessionRepositoryProvider;
protected AbstractSessionRepositoryValidator(SessionProperties sessionProperties,
ObjectProvider<?> sessionRepositoryProvider) {
this.sessionProperties = sessionProperties;
this.sessionRepositoryProvider = sessionRepositoryProvider;
}
@PostConstruct
public void checkSessionRepository() {
StoreType storeType = this.sessionProperties.getStoreType();
if (storeType != StoreType.NONE
&& this.sessionRepositoryProvider.getIfAvailable() == null
&& storeType != null) {
throw new SessionRepositoryUnavailableException(
"No session repository could be auto-configured, check your "
+ "configuration (session store type is '"
+ storeType.name().toLowerCase(Locale.ENGLISH) + "')",
storeType);
}
}
}
/**
* Bean used to validate that a SessionRepository exists and provide a
* meaningful message if that's not the case.
*/
static class ServletSessionRepositoryValidator
extends AbstractSessionRepositoryValidator {
ServletSessionRepositoryValidator(SessionProperties sessionProperties,
ObjectProvider<SessionRepository<?>> sessionRepositoryProvider) {
super(sessionProperties, sessionRepositoryProvider);
}
}
/**
* Bean used to validate that a ReactiveSessionRepository exists and provide a
* meaningful message if that's not the case.
*/
static class ReactiveSessionRepositoryValidator
extends AbstractSessionRepositoryValidator {
ReactiveSessionRepositoryValidator(SessionProperties sessionProperties,
ObjectProvider<ReactiveSessionRepository<?>> sessionRepositoryProvider) {
super(sessionProperties, sessionRepositoryProvider);
}
}
}
从以上源代码可以看出,SessionAutoConfiguration自身没有提供任何配置方法或者进行任何bean定义,其配置效果主要通过自身所使用的注解和它的嵌套配置类来完成。
SessionAutoConfiguration自身的注解约定了如下配置效果 :
SessionAutoConfiguration生效的条件- 类
Session必须存在于classpath上,换句话讲,也就是要求必须依赖包Spring Session Core; - 当前应用必须是一个
Web应用:Servlet Web应用,Reactive Web应用均可
- 类
- 导入了如下配置到相应的
bean
– 前缀为server的配置项到类型为ServerProperties的bean
– 前缀为spring.session的配置项到类型为SessionProperties的bean SessionAutoConfiguration自动配置(以及嵌套配置)的执行时机- 在以下自动配置执行之后
这些自动配置主要是配置
Spring Session存储库机制所使用底层基础设施,所以要在SessionAutoConfiguration之前完成DataSourceAutoConfigurationHazelcastAutoConfigurationJdbcTemplateAutoConfigurationMongoDataAutoConfigurationMongoReactiveDataAutoConfigurationRedisAutoConfiguration
- 在以下自动配置执行之前
HttpHandlerAutoConfiguration
- 在以下自动配置执行之后
通过所使用的注解,SessionAutoConfiguration声明了自身生效的条件和时机,然后在相应的条件生效,相应的时机到达时,SessionAutoConfiguration又将具体的配置任务委托给自己所包含的嵌套配置类来完成 :
ServletSessionConfiguration– 针对Servlet Web环境ReactiveSessionConfiguration– 针对Reactive Web环境
ServletSessionConfiguration和ReactiveSessionConfiguration这两个配置类的工作模式很类似。这里仅仅分析一下对应于比较常用的Servlet Web环境的ServletSessionConfiguration。
ServletSessionConfiguration配置类定义了一个bean :
DefaultCookieSerializer cookieSerializer仅在条件
DefaultCookieSerializerCondition被满足时定义 :Bean HttpSessionIdResolver和CookieSerializer都不存在 或者Bean CookieHttpSessionIdResolver存在 但bean CookieSerializer不存在
- 导入配置类
SessionRepositoryFilterConfiguration用于配置注册SessionRepositoryFilter到Servlet容器的FilterRegistrationBeanSessionRepositoryFilter是Spring Session机制在运行时工作的核心组件,用于服务用户请求处理过程中所有HttpSession操作请求 - 导入验证器组件
ServletSessionRepositoryValidator确保只存在一个SessionRepository bean - 定义嵌套配置类
ServletSessionRepositoryConfiguration- 仅在
bean SessionRepository不存在时生效 - 导入
ServletSessionConfigurationImportSelector以选择合适的存储库配置类针对本文所使用的应用的情形,最终会选择
RedisSessionConfiguration。
RedisSessionConfiguration配置类会应用sping.session/spring.session.redis为前缀的配置项,并定义如下bean:RedisOperationsSessionRepository sessionRepository, (重要)RedisMessageListenerContainer redisMessageListenerContainer,InitializingBean enableRedisKeyspaceNotificationsInitializer,SessionRepositoryFilter springSessionRepositoryFilter, (重要)SessionEventHttpSessionListenerAdapter sessionEventHttpSessionListenerAdapter,
- 导入验证器组件
ServletSessionRepositoryImplementationValidator以确保相应的存储库配置类存在于classpath
- 仅在
总结
通过上面分析可见,SessionAutoConfiguration自动配置会检测相应的条件然后在相应的时机执行自己的配置任务,它自身以及它委托的配置类通过逐层条件判断,最终在一个基于Redis和Servlet的Spring Boot Web应用中,会最终使用RedisSessionConfiguration进行相应的Spring Session工作组件的配置,其最重要的目的是生成一个SessionRepositoryFilter,而SessionAutoConfiguration所导入的配置类SessionRepositoryFilterConfiguration会将该SessionRepositoryFilter注册到Servlet容器。在这些工作都完成后,用户请求处理过程中对HttpSession的各种操作才会由Spring Session机制来服务。
接下来,我会在其他篇幅中继续讲解RedisSessionConfiguration和SessionRepositoryFilterConfiguration。

本文深入剖析SpringBoot中SpringSession的配置流程,重点解读SessionAutoConfiguration如何在适当条件下引入关键组件,确保基于Redis的SpringSession正确配置。
443

被折叠的 条评论
为什么被折叠?



