文章目录

  • ​​为什么要用springsession​​
  • ​​使用spring-session​​
  • ​​pom​​
  • ​​配置​​
  • ​​开启springsession​​
  • ​​使用session​​
  • ​​修改json方式序列化和作用域​​
  • ​​springsession核心原理(2.6.0版本的springsession,与之前版本可能有出入,但是原理相同)​​
  • ​​RedisIndexedSessionRepository​​
  • ​​SessionRepositoryFilter​​
  • ​​SessionRepositoryRequestWrapper​​
  • ​​HttpSessionIdResolver​​
  • ​​原理简要总结​​
  • ​​站在巨人的肩膀上​​

为什么要用springsession

为了解决分布式系统下session无法共享的问题。

spring-session是spring旗下的一个项目,把servlet容器实现的httpSession替换为spring-session,专注于解决 session管理问题。可简单快速且无缝的集成到我们的应用中。

使用spring-session

pom

<!-- 整合springsession -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>

配置

spring:
session:
store-type: redis
redis:
host: 192.168.1.13
port: 6379
server:
servlet:
session:
timeout: 30m

要配置redis的连接信息

开启springsession

在配置类或者启动类中开启session共享

@EnableRedisHttpSession     //整合Redis作为session存储

使用session

正常使用session,就会将session存入redis(data要实现Serializable)

session.setAttribute(key,data);

修改json方式序列化和作用域

@Configuration
public class GulimallSessionConfig {

@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
//放大作用域
cookieSerializer.setDomainName("cxf.com");
cookieSerializer.setCookieName("CXFSESSION");
return cookieSerializer;
}

@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}

springsession核心原理(2.6.0版本的springsession,与之前版本可能有出入,但是原理相同)

RedisIndexedSessionRepository

使用@EnableRedisHttpSession注解就会导入RedisHttpSessionConfiguration配置类,其中有一个Bean,就是用来操作session各种增删改查的类。

@Bean
public RedisIndexedSessionRepository sessionRepository() {
RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisTemplate);
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
if (this.indexResolver != null) {
sessionRepository.setIndexResolver(this.indexResolver);
}
if (this.defaultRedisSerializer != null) {
sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
}
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
if (StringUtils.hasText(this.redisNamespace)) {
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
}
sessionRepository.setFlushMode(this.flushMode);
sessionRepository.setSaveMode(this.saveMode);
int database = resolveDatabase();
sessionRepository.setDatabase(database);
this.sessionRepositoryCustomizers
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
return sessionRepository;
}

SessionRepositoryFilter

Spring Session 的核心就是这个 SessionRepositoryFilter。

它包装request和request,后续使用的request就不是原生的request了,而是包装好的,此时调用getSession方法返回的也是包装好的session,就可以直接操作Redis了。

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
// 把 HttpServletRequest 和 HttpServletResponse 装饰成 SessionRepositoryRequestWrapper
// 和 SessionRepositoryResponseWrapper,让它们走接下来的 filterChain,后续使用的request和response就是包装好的了
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,
response);

try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
wrappedRequest.commitSession();
}
}

SessionRepositoryRequestWrapper

我们可以看到,使用包装的request获取的session,也是一个包装好的session——HttpSessionWrapper,最终实现对session的增删改查是在RedisIndexedSessionRepository类中实现的。

@Override
public HttpSessionWrapper getSession(boolean create) {
// // 先从 request attribute 获取,同一个 request 的 session 会放在这
HttpSessionWrapper currentSession = getCurrentSession();
if (currentSession != null) {
return currentSession;
}
// // 获取不到,就解析 sessionId,然后基于此从 repository 获取
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 {
// This is an invalid session id. No need to ask again if
// request.getSession is invoked for the duration of this request
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
}
setAttribute(INVALID_SESSION_ID_ATTR, "true");
}
if (!create) {
return null;
}
if (SessionRepositoryFilter.this.httpSessionIdResolver instanceof CookieHttpSessionIdResolver
&& this.response.isCommitted()) {
throw new IllegalStateException("Cannot create a session after the response has been committed");
}
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
+ SESSION_LOGGER_NAME,
new RuntimeException("For debugging purposes only (not an error)"));
}
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
session.setLastAccessedTime(Instant.now());
currentSession = new HttpSessionWrapper(session, getServletContext());
setCurrentSession(currentSession);
return currentSession;
}

HttpSessionIdResolver

public interface HttpSessionIdResolver {

// 从客户端请求解析 sessionId
List<String> resolveSessionIds(HttpServletRequest request);

// 告诉客户端新创建 session 的 sessionId,比如放到 Cookie 里
void setSessionId(HttpServletRequest request, HttpServletResponse response, String sessionId);

// 告知客户端 session 过期,比如从 Cookie 里移除对应的 sessionId
void expireSession(HttpServletRequest request, HttpServletResponse response);

}

该接口主要负责 sessionId 的解析、处理工作
resolveSessionIds 方法解析客户端请求对应的 sessionId,比如从 Cookie 解析、 RequestHeader 解析
setSessionId 方法将创建的 sessionId 返回给客户端,比如放到 Cookie 、 ResponseHeader 里
expireSession 方法告知客户端 session 已过期,比如从 Cookie 移除、删除对应的 ResponseHeader
Spring 提供的实现有 CookieHttpSessionIdResolver 和 HeaderHttpSessionIdResolver,前者是基于 Cookie 后者基于 Header,默认的是 CookieHttpSessionIdResolver

原理简要总结

当请求进来的时候,SessionRepositoryFilter 会先拦截到请求,将 request 和 response 对象转换成 SessionRepositoryRequestWrapper 和 SessionRepositoryResponseWrapper 。后续当第一次调用 request 的getSession方法时,会调用到 SessionRepositoryRequestWrapper 的getSession方法。这个方法是被从写过的,逻辑是先从 request 的属性中查找,如果找不到;再查找一个key值是"SESSION"的 Cookie,通过这个 Cookie 拿到 SessionId 去 Redis 中查找,如果查不到,就直接创建一个RedisSession 对象,同步到 Redis 中。

说的简单点就是:拦截请求,将之前在服务器内存中进行 Session 创建销毁的动作,改成在 Redis 中创建。