本文基于以下组合的应用,通过源代码分析一下一个Spring Boot
应用中Spring Session
的配置过程:
Spring Boot 2.1.3.RELEASE
Spring Session Core 2.1.4.RELEASE
Spring Session Data Redis 2.1.3.RELEASE
Spring 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
之前完成
DataSourceAutoConfiguration
HazelcastAutoConfiguration
JdbcTemplateAutoConfiguration
MongoDataAutoConfiguration
MongoReactiveDataAutoConfiguration
RedisAutoConfiguration
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
容器的FilterRegistrationBean
SessionRepositoryFilter
是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
。