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

概述

本文基于以下组合的应用,通过源代码分析一下一个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自身的注解约定了如下配置效果 :

  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,Session,Spring,Boot,自动配置)