1 自动装配
public class SessionAutoConfiguration {
// SessionRepositoryFilterConfiguration用来配置核心的过滤器
// 3 核心过滤器
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@Import({ ServletSessionRepositoryValidator.class, SessionRepositoryFilterConfiguration.class })
static class ServletSessionConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(SessionRepository.class)
@Import({ ServletSessionRepositoryImplementationValidator.class,
ServletSessionConfigurationImportSelector.class })
static class ServletSessionRepositoryConfiguration {
}
}
// 该类主要作用就是用来更加当前环境下的所有类型的*SessionConfiguration
// 如:RedisSessionConfiguration,JdbcSessionConfiguration等。
// 2 核心Session配置对象
static class ServletSessionConfigurationImportSelector extends SessionConfigurationImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return super.selectImports(WebApplicationType.SERVLET);
}
}
}
2 核心Session配置对象
在上每一步中会获取容器中所有注册的*SessionConfiguration。
- RedisSessionConfiguration,RedisReactiveSessionConfiguration
- MongoSessionConfiguration,MongoReactiveSessionConfiguration
- JdbcSessionConfiguration
- HazelcastSessionConfiguration
- NoOpSessionConfiguration,NoOpReactiveSessionConfiguration
这些类都是在如下类中注册
final class SessionStoreMappings {
private static final Map<StoreType, Configurations> MAPPINGS;
static {
Map<StoreType, Configurations> mappings = new EnumMap<>(StoreType.class);
mappings.put(StoreType.REDIS,
new Configurations(RedisSessionConfiguration.class, RedisReactiveSessionConfiguration.class));
mappings.put(StoreType.MONGODB,
new Configurations(MongoSessionConfiguration.class, MongoReactiveSessionConfiguration.class));
mappings.put(StoreType.JDBC, new Configurations(JdbcSessionConfiguration.class, null));
mappings.put(StoreType.HAZELCAST, new Configurations(HazelcastSessionConfiguration.class, null));
mappings.put(StoreType.NONE,
new Configurations(NoOpSessionConfiguration.class, NoOpReactiveSessionConfiguration.class));
MAPPINGS = Collections.unmodifiableMap(mappings);
}
}
2.1 注册Session配置类
上面列出了系统中所有的*SessionConfiguration配置类,那具体该注册哪一个?
回到上面的
ServletSessionConfigurationImportSelector中
进入
ServletSessionConfigurationImportSelector#selectImports方法:
abstract static class SessionConfigurationImportSelector implements ImportSelector {
protected final String[] selectImports(WebApplicationType webApplicationType) {
// 这里就是迭代上面登记的所有*SessionConfiguration类
return Arrays.stream(StoreType.values())
.map((type) -> SessionStoreMappings.getConfigurationClass(webApplicationType, type))
.toArray(String[]::new);
}
}
获取到所有的配置类后,如何进行选择该注册哪一个配置类?这里我们打开*SessionConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RedisTemplate.class, RedisIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(RedisSessionProperties.class)
class RedisSessionConfiguration {
@Configuration(proxyBeanMethods = false)
public static class SpringBootRedisHttpSessionConfiguration extends RedisHttpSessionConfiguration {
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ JdbcTemplate.class, JdbcIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(DataSource.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(JdbcSessionProperties.class)
class JdbcSessionConfiguration {
@Configuration(proxyBeanMethods = false)
static class SpringBootJdbcHttpSessionConfiguration extends JdbcHttpSessionConfiguration {
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ MongoOperations.class, MongoIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(MongoOperations.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(MongoSessionProperties.class)
class MongoSessionConfiguration {
@Configuration
public static class SpringBootMongoHttpSessionConfiguration extends MongoHttpSessionConfiguration {
}
}
@Conditional(ServletSessionCondition.class)
@ConditionalOnMissingBean(SessionRepository.class)
class HazelcastSessionConfiguration {
}
@Conditional(ServletSessionCondition.class)
@ConditionalOnMissingBean(SessionRepository.class)
class NoOpSessionConfiguration {
}
这些类每一种存储类型它都有相应的注册条件,只有满足条件的才能被注册。
注意:
这些类是通过ImportSelector导入进行注册的,这时候就需要注意了,如果一个类是通过@Import导入的,那么只有导入的这个类能被注册,该类的内部配置类才能被注册,反之,被导入的不能被注册,那么这个类的内部配置类也不会被注册。如下RedisSessionConfiguration,如果这个类不能被注册,那么内部类
SpringBootRedisHttpSessionConfiguration也不能被注册。
class RedisSessionConfiguration {
@Configuration(proxyBeanMethods = false)
public static class SpringBootRedisHttpSessionConfiguration extends RedisHttpSessionConfiguration {
@Autowired
public void customize(SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties) {
Duration timeout = sessionProperties.getTimeout();
if (timeout != null) {
setMaxInactiveIntervalInSeconds((int) timeout.getSeconds());
}
setRedisNamespace(redisSessionProperties.getNamespace());
setFlushMode(redisSessionProperties.getFlushMode());
setSaveMode(redisSessionProperties.getSaveMode());
setCleanupCron(redisSessionProperties.getCleanupCron());
}
}
}
如果一个配置类本身在容器启动的时候就能被容器扫描到,那么如果该类即便不能被注册,但是他的内部配置类还是可以被注册的。如下情况:
@Configuration
@ConditionalOnProperty(prefix = "s", name = "n", havingValue = "1", matchIfMissing = false)
public class InnerConfiguration {
public InnerConfiguration() {
System.out.println("===============") ;
}
@Configuration
static class Inner {
public Inner() {
System.out.println("--------------") ;
}
}
}
如果上面的类内被容器启动的时候扫描到,但是这个类本身没有满足条件不能被注册,但是它的内部配置类Inner还是会被容器扫描到进行注册的。因为容器启动的时候会扫描启动类所在的包及其子包下的所有*.class文件,Inner这个内部类也是一个class文件。
再看ServletSessionCondition条件注册类
class ServletSessionCondition extends AbstractSessionCondition {
ServletSessionCondition() {
super(WebApplicationType.SERVLET);
}
}
abstract class AbstractSessionCondition extends SpringBootCondition {
private final WebApplicationType webApplicationType;
protected AbstractSessionCondition(WebApplicationType webApplicationType) {
this.webApplicationType = webApplicationType;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("Session Condition");
Environment environment = context.getEnvironment();
StoreType required = SessionStoreMappings.getType(this.webApplicationType,
((AnnotationMetadata) metadata).getClassName());
if (!environment.containsProperty("spring.session.store-type")) {
return ConditionOutcome.match(message.didNotFind("property", "properties")
.items(ConditionMessage.Style.QUOTE, "spring.session.store-type"));
}
try {
Binder binder = Binder.get(environment);
// 将spring.session.store-type配置属性绑定到StoreType枚举对象上
return binder.bind("spring.session.store-type", StoreType.class)
// 判断配置的类型是否与当前处理的类上的相同。
.map((t) -> new ConditionOutcome(t == required,
message.found("spring.session.store-type property").items(t)))
.orElse(ConditionOutcome.noMatch(message.didNotFind("spring.session.store-type property").atAll()));
}
}
}
2.2 注册Session存储对象
这里以Redis为例,上面的
SpringBootRedisHttpSessionConfiguration继承
RedisHttpSessionConfiguration类进入
@Configuration(proxyBeanMethods = false)
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
// 注册一个SessionRepository类型的Session存储对象
@Bean
public RedisIndexedSessionRepository sessionRepository() {
// ...
}
}
到这里最为关键的一个SessionRepository对象就创建注册了。
RedisIndexedSessionRepository类继承自SessionRepository接口。
3 核心过滤器
3.1 过滤器注册
class SessionRepositoryFilterConfiguration {
// 这里的SessionRepositoryFilter是核心的处理Session的过滤器
// 而关于该种过滤器的注册方式可参考SpringSecurity.md文档
@Bean
FilterRegistrationBean<SessionRepositoryFilter<?>> sessionRepositoryFilterRegistration(
SessionProperties sessionProperties, SessionRepositoryFilter<?> filter) {
FilterRegistrationBean<SessionRepositoryFilter<?>> registration = new FilterRegistrationBean<>(filter);
registration.setDispatcherTypes(getDispatcherTypes(sessionProperties));
registration.setOrder(sessionProperties.getServlet().getFilterOrder());
return registration;
}
}
在2.2中
RedisHttpSessionConfiguration继承自
SpringHttpSessionConfiguration进入该类
@Configuration(proxyBeanMethods = false)
public class SpringHttpSessionConfiguration implements ApplicationContextAware {
// 注入了在上一步中创建的核心Session存储对象RedisIndexedSessionRepository
// 该过滤器对象会被注册到Servlet容器中
@Bean
public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(
SessionRepository<S> sessionRepository) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(sessionRepository);
sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
return sessionRepositoryFilter;
}
}
3.2 过滤器核心方法
接下来查看该过滤器的一些核心方法
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
// 核心就是这里,分别自定义了Request,Response对象进行了重新包装
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,
response);
try {
// 将自定义的Request,Response向下传递,这在使用了Spring Security就非常方便了。
filterChain.doFilter(wrappedRequest, wrappedResponse);
} finally {
// 这就就是触发讲所有向Session中存入的对象保存到对应的实现中(如:Redis或JDBC)
wrappedRequest.commitSession();
}
}
}
接着查看
SessionRepositoryRequestWrapper包装类中重写的几个核心方法
private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
// 提交Session中的数据保存到具体的实现中,如(Redis,JDBC等)
private void commitSession() {
HttpSessionWrapper wrappedSession = getCurrentSession();
if (wrappedSession == null) {
if (isInvalidateClientSession()) {
SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this, this.response);
}
} else {
S session = wrappedSession.getSession();
clearRequestedSessionCache();
SessionRepositoryFilter.this.sessionRepository.save(session);
String sessionId = session.getId();
if (!isRequestedSessionIdValid() || !sessionId.equals(getRequestedSessionId())) {
SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this, this.response, sessionId);
}
}
}
// 当通过HttpServletRequest获取HttpSession对象的时候就是调用的该方法了。
@Override
public HttpSessionWrapper getSession(boolean create) {
HttpSessionWrapper currentSession = getCurrentSession();
if (currentSession != null) {
return currentSession;
}
S requestedSession = getRequestedSession();
if (requestedSession != null) {
if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
requestedSession.setLastAccessedTime(Instant.now());
this.requestedSessionIdValid = true;
currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
currentSession.markNotNew();
setCurrentSession(currentSession);
return currentSession;
}
} else {
setAttribute(INVALID_SESSION_ID_ATTR, "true");
}
if (!create) {
return null;
}
// session = MapSession该对象内部维护了一个Map集合
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
session.setLastAccessedTime(Instant.now());
// 这又是自定义的Session对象
currentSession = new HttpSessionWrapper(session, getServletContext());
setCurrentSession(currentSession);
return currentSession;
}
@Override
public HttpSessionWrapper getSession() {
return getSession(true);
}
// 实际操作的Session对象就是该实现
private final class HttpSessionWrapper extends HttpSessionAdapter<S> {
HttpSessionWrapper(S session, ServletContext servletContext) {
super(session, servletContext);
}
@Override
public void invalidate() {
super.invalidate();
SessionRepositoryRequestWrapper.this.requestedSessionInvalidated = true;
setCurrentSession(null);
clearRequestedSessionCache();
SessionRepositoryFilter.this.sessionRepository.deleteById(getId());
}
}
}
像Session中操作数据核心方法是setAttribute,getAttribute
HttpSessionWrapper继承HttpSessionWrapper
class HttpSessionAdapter<S extends Session> implements HttpSession {
// MapSession 内部维护了一个Map集合,专门用来存数据的
private S session;
public Object getAttribute(String name) {
return this.session.getAttribute(name);
}
public void setAttribute(String name, Object value) {
checkState();
// 调用MapSession对象方法,获取内部Map中的值信息
Object oldValue = this.session.getAttribute(name);
// 调用MapSession对象方法,将键值存入到内部维护的Map中
this.session.setAttribute(name, value);
if (value != oldValue) {
if (oldValue instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener) oldValue)
.valueUnbound(new HttpSessionBindingEvent(this, name, oldValue));
}
}
if (value instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name, value));
}
}
}
}
}
该过滤器的作用及工作流程总结如下:
- 使用自定义的Request,Response对象将这2个对象通过FilterChain#doFilter方法向后传递,供其它的过滤器使用。
- 其它过滤器在使用Session过程中都是使用的上一步中传下来的自定义Request对象SessionRepositoryRequestWrapper
- 目标对象执行完后返回时会继续执行FilterChain#doFilter剩下的代码,也就是上面的SessionRepositoryRequestWrapper#commitSession方法,该方法的多用就是提交在后续的Filter或者目标对象(如:Controller)中对Session对象的操作,将这些信息提交多相应的存储对象上,如:Redis或者JDBC等中。
3.3 Session数据存储
这里我们查看关于
SessionRepositoryRequestWrapper#commitSession方法的执行。
根据上面还是以Redis实现为例,Session的存储对象是
RedisIndexedSessionRepository
private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
private void commitSession() {
HttpSessionWrapper wrappedSession = getCurrentSession();
if (wrappedSession == null) {
if (isInvalidateClientSession()) {
SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this, this.response);
}
} else {
S session = wrappedSession.getSession();
clearRequestedSessionCache();
// 保存session里的信息
SessionRepositoryFilter.this.sessionRepository.save(session);
}
}
}
RedisIndexedSessionRepository对象
public class RedisIndexedSessionRepository
implements FindByIndexNameSessionRepository<RedisIndexedSessionRepository.RedisSession>, MessageListener {
public void save(RedisSession session) {
session.save();
if (session.isNew) {
String sessionCreatedKey = getSessionCreatedChannel(session.getId());
this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
session.isNew = false;
}
}
}
RedisSession对象
final class RedisSession implements Session {
// 该Map中存了所有的Session信息
private Map<String, Object> delta = new HashMap<>();
private void save() {
saveChangeSessionId();
// 这里是核心
saveDelta();
}
private void saveDelta() {
if (this.delta.isEmpty()) {
return;
}
String sessionId = getId();
// 将所有的数据保存到Redis中。
getSessionBoundHashOperations(sessionId).putAll(this.delta);
String principalSessionKey = getSessionAttrNameKey(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
String securityPrincipalSessionKey = getSessionAttrNameKey(SPRING_SECURITY_CONTEXT);
if (this.delta.containsKey(principalSessionKey) || this.delta.containsKey(securityPrincipalSessionKey)) {
if (this.originalPrincipalName != null) {
String originalPrincipalRedisKey = getPrincipalKey(this.originalPrincipalName);
RedisIndexedSessionRepository.this.sessionRedisOperations.boundSetOps(originalPrincipalRedisKey)
.remove(sessionId);
}
Map<String, String> indexes = RedisIndexedSessionRepository.this.indexResolver.resolveIndexesFor(this);
String principal = indexes.get(PRINCIPAL_NAME_INDEX_NAME);
this.originalPrincipalName = principal;
if (principal != null) {
String principalRedisKey = getPrincipalKey(principal);
RedisIndexedSessionRepository.this.sessionRedisOperations.boundSetOps(principalRedisKey)
.add(sessionId);
}
}
// 将数据存储完成后将delta集合清空(这里可以避免重复提交数据)
this.delta = new HashMap<>(this.delta.size());
// 下面就是更新key的过期时间
Long originalExpiration = (this.originalLastAccessTime != null)
? this.originalLastAccessTime.plus(getMaxInactiveInterval()).toEpochMilli() : null;
RedisIndexedSessionRepository.this.expirationPolicy.onExpirationUpdated(originalExpiration, this);
}
}
完毕!!!