概述

本文基于以下组合的应用,通过源代码分析一下一个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 ReactiveSessionConfigurationImportSelectorextends SessionConfigurationImportSelector {@Overridepublic 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 ServletSessionConfigurationImportSelectorextends SessionConfigurationImportSelector {@Overridepublic 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;}@PostConstructpublic 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 ServletSessionRepositoryImplementationValidatorextends 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 ReactiveSessionRepositoryImplementationValidatorextends 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;}@PostConstructpublic 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 ServletSessionRepositoryValidatorextends 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 ReactiveSessionRepositoryValidatorextends AbstractSessionRepositoryValidator {ReactiveSessionRepositoryValidator(SessionProperties sessionProperties,ObjectProvider<ReactiveSessionRepository<?>> sessionRepositoryProvider) {super(sessionProperties, sessionRepositoryProvider);}}}

从以上源代码可以看出,SessionAutoConfiguration自身没有提供任何配置方法或者进行任何bean定义,其配置效果主要通过自身所使用的注解和它的嵌套配置类来完成。

SessionAutoConfiguration自身的注解约定了如下配置效果 :

  1. SessionAutoConfiguration生效的条件

    • Session必须存在于classpath上,换句话讲,也就是要求必须依赖包Spring Session Core;
    • 当前应用必须是一个Web应用:Servlet Web应用,Reactive Web应用均可
  2. 导入了如下配置到相应的bean
    – 前缀为server的配置项到类型为ServerPropertiesbean
    – 前缀为spring.session的配置项到类型为SessionPropertiesbean
  3. SessionAutoConfiguration自动配置(以及嵌套配置)的执行时机
    • 在以下自动配置执行之后

      这些自动配置主要是配置Spring Session存储库机制所使用底层基础设施,所以要在SessionAutoConfiguration之前完成

      • DataSourceAutoConfiguration
      • HazelcastAutoConfiguration
      • JdbcTemplateAutoConfiguration
      • MongoDataAutoConfiguration
      • MongoReactiveDataAutoConfiguration
      • RedisAutoConfiguration
    • 在以下自动配置执行之前
      • HttpHandlerAutoConfiguration

通过所使用的注解,SessionAutoConfiguration声明了自身生效的条件和时机,然后在相应的条件生效,相应的时机到达时,SessionAutoConfiguration又将具体的配置任务委托给自己所包含的嵌套配置类来完成 :

  • ServletSessionConfiguration – 针对Servlet Web环境
  • ReactiveSessionConfiguration – 针对Reactive Web 环境

ServletSessionConfigurationReactiveSessionConfiguration这两个配置类的工作模式很类似。这里仅仅分析一下对应于比较常用的Servlet Web环境的ServletSessionConfiguration

ServletSessionConfiguration配置类定义了一个bean :

  • DefaultCookieSerializer cookieSerializer

    仅在条件DefaultCookieSerializerCondition被满足时定义 :

    1. Bean HttpSessionIdResolverCookieSerializer 都不存在 或者
    2. Bean CookieHttpSessionIdResolver 存在 但 bean CookieSerializer 不存在
  • 导入配置类SessionRepositoryFilterConfiguration用于配置注册SessionRepositoryFilterServlet容器的FilterRegistrationBean

    SessionRepositoryFilterSpring Session机制在运行时工作的核心组件,用于服务用户请求处理过程中所有HttpSession操作请求

  • 导入验证器组件ServletSessionRepositoryValidator确保只存在一个SessionRepository bean
  • 定义嵌套配置类ServletSessionRepositoryConfiguration
    • 仅在bean SessionRepository不存在时生效
    • 导入ServletSessionConfigurationImportSelector以选择合适的存储库配置类

      针对本文所使用的应用的情形,最终会选择RedisSessionConfiguration
      RedisSessionConfiguration配置类会应用sping.session/spring.session.redis为前缀的配置项,并定义如下bean :

      1. RedisOperationsSessionRepository sessionRepository, (重要)
      2. RedisMessageListenerContainer redisMessageListenerContainer,
      3. InitializingBean enableRedisKeyspaceNotificationsInitializer,
      4. SessionRepositoryFilter springSessionRepositoryFilter, (重要)
      5. SessionEventHttpSessionListenerAdapter sessionEventHttpSessionListenerAdapter,
    • 导入验证器组件ServletSessionRepositoryImplementationValidator以确保相应的存储库配置类存在于classpath

总结

通过上面分析可见,SessionAutoConfiguration自动配置会检测相应的条件然后在相应的时机执行自己的配置任务,它自身以及它委托的配置类通过逐层条件判断,最终在一个基于RedisServletSpring Boot Web应用中,会最终使用RedisSessionConfiguration进行相应的Spring Session工作组件的配置,其最重要的目的是生成一个SessionRepositoryFilter,而SessionAutoConfiguration所导入的配置类SessionRepositoryFilterConfiguration会将该SessionRepositoryFilter注册到Servlet容器。在这些工作都完成后,用户请求处理过程中对HttpSession的各种操作才会由Spring Session机制来服务。

接下来,我会在其他篇幅中继续讲解RedisSessionConfigurationSessionRepositoryFilterConfiguration

相关文章

  • Spring Boot 应用中 Spring Session 的配置(2) : 基于Redis的配置 RedisSessionConfiguration
  • Spring Boot 应用中 Spring Session 的配置(3) : SessionRepositoryFilterConfiguration

Spring Boot 应用中 Spring Session 的配置(1) : 自动配置 SessionAutoConfiguration相关推荐

  1. Spring Boot——开发新一代Spring Java应用

    2019独角兽企业重金招聘Python工程师标准>>> Spring官方网站本身使用Spring框架开发,随着功能以及业务逻辑的日益复杂,应用伴随着大量的XML配置文件以及复杂的Be ...

  2. Spring Boot——开发新一代Spring应用

    Spring官方网站本身使用Spring框架开发,随着功能以及业务逻辑的日益复杂,应用伴随着大量的XML配置文件以及复杂的Bean依赖关系.随着Spring 3.0的发布,Spring IO团队逐渐开 ...

  3. mybatis 配置_配置Mybatis在Spring Boot工程中的整合

    配置Mybatis在Spring Boot工程中的整合包,设置mybatis的实体类别名,输出执行sql语句配置项. 分析: 添加启动器依赖: 配置Mybatis:实体类别名包,日志,映射文件等: 配 ...

  4. Guava Cache本地缓存在 Spring Boot应用中的实践

    概述 在如今高并发的互联网应用中,缓存的地位举足轻重,对提升程序性能帮助不小.而 3.x开始的 Spring也引入了对 Cache的支持,那对于如今发展得如火如荼的 Spring Boot来说自然也是 ...

  5. spring boot 项目源码_Spring Boot2 系列教程(三)理解 Spring Boot 项目中的 parent

    前面和大伙聊了 Spring Boot 项目的三种创建方式,这三种创建方式,无论是哪一种,创建成功后,pom.xml 坐标文件中都有如下一段引用: <parent><groupId& ...

  6. 在Spring Boot测试中使用Testcontainer进行数据库集成测试

    在此博客文章中,我想演示如何在Spring Boot测试中集成Testcontainer以便与数据库一起运行集成测试. 我没有使用Testcontainers的Spring Boot模块. 如何与他们 ...

  7. Spring Boot框架中使用Jackson的处理总结

    1.前言 通常我们在使用Spring Boot框架时,如果没有特别指定接口的序列化类型,则会使用Spring Boot框架默认集成的Jackson框架进行处理,通过Jackson框架将服务端响应的数据 ...

  8. 【Spring Boot】3.Spring Boot的配置

    2019独角兽企业重金招聘Python工程师标准>>> 简介 springboot使用一个全局配置文件,配置文件名是固定的: application.properties appli ...

  9. Spring Boot 2.x的默认日志管理与Logback配置详解

    前沿技术早知道,弯道超车有希望 积累超车资本,从关注DD开始 Spring Boot在所有内部日志中使用Commons Logging,但是对底层日志的实现是开放的.在Spring Boot生态中,为 ...

最新文章

  1. Redis的优势和特点
  2. Mongodb 定时备份和恢复
  3. 【哲学】《哲学的故事》笔记
  4. 进程控制1--fork vfork函数
  5. html页面图片翻转特效代码,如何使用css实现翻转图片的效果(附代码)
  6. 数组取值_Python基础(五)--numpy包(数组与矩阵)
  7. c语言会生成class文件,一文带你刨析class文件
  8. freeswitch 文件包含关系图
  9. 物业缴费管理系统 微小区 物业社区公众号开发 物业APP小程序源码
  10. 3GPP TS 23502-h20 中英文对照 | 4.15.6.2 NEF service operations information flow
  11. 复盘图像双线性插值推导细节
  12. c语言简单计算器减编程,C语言实现简单的计算器(加、减、乘、除)
  13. 2020-2021阿里巴巴Java面试真题解析
  14. c语言求开平方标准库函数,关于C语言中的开方计算,首先想到的当然是sqrt()函数,让我们先来回顾一下它的基本用法: 头文件:#include math.hsqrt(...
  15. 铁路警方启用AI眼镜,当场抓逃犯!外媒惊叹不已!
  16. eplan连接定义点不显示_EPLAN操作命令之线色设置
  17. 在 vscode 上刷力扣 Leetcode 可以这样来
  18. Android获取系统的硬件信息、系统版本以及如何检测ROM类型
  19. ssm+java计算机毕业设计大学生就业管理系统26cjn(程序+lw+源码+远程部署)
  20. 计算机运算器由什么组成部分,什么是运算器_运算器由什么组成

热门文章

  1. 国内首例!违反 GPL 协议致侵权,被判赔偿 50 万元
  2. 投资30亿美元 IBM启动云计算大数据芯片研究计划
  3. 亚马逊全球开店戴竫斐:2021中国出口跨境电商的蜕变与破局
  4. Maven - 2、安装、配置、mvn运行过程详解
  5. 香港服务器租用不得不警惕的潜规则
  6. 深度学习机器学习理论知识:范数、稀疏与过拟合合集(5)Dropout原理,操作实现,为什么可以缓解过拟合,使用中的技巧
  7. c语言学籍管理系统设计,c语言学籍信息管理系统设计
  8. ubuntu服务器卸载mysql_在Ubuntu或Debian系统的服务器上卸载MySQL的方法
  9. django--生鲜商城项目
  10. 文正机械电子工程专业课_机械电子工程有些什么课程