项目中一直使用spring session但是以前都是只有在使用没有深入了解其原理,这次碰巧项目不是很忙仔细研究了一下他的原理
spring session 的使用
使用很简单网上一大堆教程这里面就简单说一下
引入jar包
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
复制代码
注解
@EnableRedisHttpSession(redisNamespace=“xxxx”)
复制代码
不用这个好像也可以,他可以修改你session存放在redis中的前缀
源码解析
我们主要关注一下几个类
EnableRedisHttpSession // 注解
RedisHttpSessionConfiguration // 加载redis 的配置类
RedisIndexedSessionRepository // spring session 中 redis的操作类
SpringHttpSessionConfiguration // 初始化 springsession过滤器的配置类
SessionRepositoryFilter // spring session 的过滤器,在这里获取session 创建session 保存session
CookieHttpSessionIdResolver // cookie 转化成sessionId的转化器
SessionRepositoryRequestWrapper // 重写了request的getsession方法,重写之后会从redis中获取
复制代码
通过讲解上述几个类大家基本上也就会理解整个springsession的中session如何保存进redis,又如何从redis中取出来的
EnableRedisHttpSession
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(RedisHttpSessionConfiguration.class) //首先要引入这个redissession的配置类
@Configuration(proxyBeanMethods = false)
public @interface EnableRedisHttpSession {
/**
* The session timeout in seconds. By default, it is set to 1800 seconds (30 minutes).
* This should be a non-negative integer.
* @return the seconds a session can be inactive before expiring
*/
//session 在redis中的失效时间
int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
/**
* Defines a unique namespace for keys. The value is used to isolate sessions by
* changing the prefix from default {@code spring:session:} to
* {@code <redisNamespace>:}.
* <p>
* For example, if you had an application named "Application A" that needed to keep
* the sessions isolated from "Application B" you could set two different values for
* the applications and they could function within the same Redis instance.
* @return the unique namespace for keys
*/
//在redis中的前缀名称
String redisNamespace() default RedisIndexedSessionRepository.DEFAULT_NAMESPACE;
/**
* Flush mode for the Redis sessions. The default is {@code ON_SAVE} which only
* updates the backing Redis when {@link SessionRepository#save(Session)} is invoked.
* In a web environment this happens just before the HTTP response is committed.
* <p>
* Setting the value to {@code IMMEDIATE} will ensure that the any updates to the
* Session are immediately written to the Redis instance.
* @return the {@link RedisFlushMode} to use
* @since 1.1
* @deprecated since 2.2.0 in favor of {@link #flushMode()}
*/
@Deprecated
RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;
复制代码
看了上述的代码,应该对这个有了一个大致的概念,在项目中我们是经常要设置session的失效时间,不同的服务redis中的保存前缀也是不同的,之后我们也应该知道接下来我们要分析哪一个类了,没错就是他要引入的RedisHttpSessionConfiguration这个类
RedisHttpSessionConfiguration
在这个类里面你可以理解它主要就是配置了一些redis相关的东西
// sessionRepository主要就是 session 保存在redis中的保存期
@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;
}
// redis 的监听器,一般监听的是 session在redis中删除或者是到达了失效时间
@Bean
public RedisMessageListenerContainer springSessionRedisMessageListenerContainer(
RedisIndexedSessionRepository sessionRepository) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(this.redisConnectionFactory);
if (this.redisTaskExecutor != null) {
container.setTaskExecutor(this.redisTaskExecutor);
}
if (this.redisSubscriptionExecutor != null) {
container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
}
container.addMessageListener(sessionRepository,
Arrays.asList(new ChannelTopic(sessionRepository.getSessionDeletedChannel()),
new ChannelTopic(sessionRepository.getSessionExpiredChannel())));
container.addMessageListener(sessionRepository,
Collections.singletonList(new PatternTopic(sessionRepository.getSessionCreatedChannelPrefix() + "*")));
return container;
}
复制代码
RedisIndexedSessionRepository
这个类中主要就是redis的相关操作,主要我们可以关注一下两个方法:
1 保存session
2 通过sessionId获取session
@Override
public void save(RedisSession session) {
session.save();
if (session.isNew) {
String sessionCreatedKey = getSessionCreatedChannel(session.getId());
this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
session.isNew = false;
}
}
public void cleanupExpiredSessions() {
this.expirationPolicy.cleanExpiredSessions();
}
@Override
public RedisSession findById(String id) {
return getSession(id, false);
}
复制代码
SpringHttpSessionConfiguration
@Bean
public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(
SessionRepository<S> sessionRepository) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(sessionRepository);
sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver); //sessionId转化器
return sessionRepositoryFilter;
}
复制代码
在这个配置类中主要注册了sessionRepositoryFilter 过滤器,可以看见sessionRepositoryFilter 设置了一个HttpSessionIdResolver,看名字就可以知道这是sessionId的转化器,就是生成sessionId的的类,可以看一下代码中默认的转化器
@Configuration(proxyBeanMethods = false)
public class SpringHttpSessionConfiguration implements ApplicationContextAware {
private final Log logger = LogFactory.getLog(getClass());
private CookieHttpSessionIdResolver defaultHttpSessionIdResolver = new CookieHttpSessionIdResolver();
private boolean usesSpringSessionRememberMeServices;
private ServletContext servletContext;
private CookieSerializer cookieSerializer;
private HttpSessionIdResolver httpSessionIdResolver = this.defaultHttpSessionIdResolver;
复制代码
通过上述代码就可以清晰的看见默认的sessionId转化器是CookieHttpSessionIdResolver,接下来看一下sessionId是如何生成
CookieHttpSessionIdResolver
//通过cookie获取sessionId
@Override
public List<String> resolveSessionIds(HttpServletRequest request) {
return this.cookieSerializer.readCookieValues(request);
}
// 将sessionId 写回cookie中
@Override
public void setSessionId(HttpServletRequest request, HttpServletResponse response, String sessionId) {
if (sessionId.equals(request.getAttribute(WRITTEN_SESSION_ID_ATTR))) {
return;
}
request.setAttribute(WRITTEN_SESSION_ID_ATTR, sessionId);
this.cookieSerializer.writeCookieValue(new CookieValue(request, response, sessionId));
}
//具体的方法获取sessionId
@Override
public List<String> readCookieValues(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
List<String> matchingCookieValues = new ArrayList<>();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (this.cookieName.equals(cookie.getName())) {
String sessionId = (this.useBase64Encoding ? base64Decode(cookie.getValue()) : cookie.getValue());
if (sessionId == null) {
continue;
}
if (this.jvmRoute != null && sessionId.endsWith(this.jvmRoute)) {
sessionId = sessionId.substring(0, sessionId.length() - this.jvmRoute.length());
}
matchingCookieValues.add(sessionId);
}
}
}
return matchingCookieValues;
}
复制代码
通过上述代码就可以知道resolveSessionIds获取sessionId, resolveSessionIds真正的执行方法是 readCookieValues,可以看见sessionId的获取的方法实际就是将cookie中的value进行base64转码。
SessionRepositoryFilter
接下来就是重中之重了spring session过滤器,一切的一切都是从这里开始的
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
private static final String SESSION_LOGGER_NAME = SessionRepositoryFilter.class.getName().concat(".SESSION_LOGGER");
private static final Log SESSION_LOGGER = LogFactory.getLog(SESSION_LOGGER_NAME);
/**
* The session repository request attribute name.
*/
public static final String SESSION_REPOSITORY_ATTR = SessionRepository.class.getName();
/**
* Invalid session id (not backed by the session repository) request attribute name.
*/
public static final String INVALID_SESSION_ID_ATTR = SESSION_REPOSITORY_ATTR + ".invalidSessionId";
private static final String CURRENT_SESSION_ATTR = SESSION_REPOSITORY_ATTR + ".CURRENT_SESSION";
/**
* The default filter order.
*/
public static final int DEFAULT_ORDER = Integer.MIN_VALUE + 50;
private final SessionRepository<S> sessionRepository;
private HttpSessionIdResolver httpSessionIdResolver = new CookieHttpSessionIdResolver();
/**
* Creates a new instance.
* @param sessionRepository the <code>SessionRepository</code> to use. Cannot be null.
*/
public SessionRepositoryFilter(SessionRepository<S> sessionRepository) {
if (sessionRepository == null) {
throw new IllegalArgumentException("sessionRepository cannot be null");
}
this.sessionRepository = sessionRepository;
}
/**
* Sets the {@link HttpSessionIdResolver} to be used. The default is a
* {@link CookieHttpSessionIdResolver}.
* @param httpSessionIdResolver the {@link HttpSessionIdResolver} to use. Cannot be
* null.
*/
public void setHttpSessionIdResolver(HttpSessionIdResolver httpSessionIdResolver) {
if (httpSessionIdResolver == null) {
throw new IllegalArgumentException("httpSessionIdResolver cannot be null");
}
this.httpSessionIdResolver = httpSessionIdResolver;
}
//过滤器真正执行逻辑的方法
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,
response);
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
//在这个方法中会将session保存在redis中
wrappedRequest.commitSession();
}
}
复制代码
上面就是filter具体的执行逻辑,这里我们首先看一下SessionRepositoryRequestWrapper这个类,该类主要关注的就是getSession方法,他重写了request的getSession方法,变成了从redis中获取
SessionRepositoryRequestWrapper
写出几个关键的方法
private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
private final HttpServletResponse response;
private S requestedSession;
private boolean requestedSessionCached;
private String requestedSessionId;
private Boolean requestedSessionIdValid;
private boolean requestedSessionInvalidated;
private SessionRepositoryRequestWrapper(HttpServletRequest request, HttpServletResponse response) {
super(request);
this.response = response;
}
/**
* Uses the {@link HttpSessionIdResolver} to write the session id to the response
* and persist the Session.
*/
---------------------------------------------
//保存session进redis中,设置超时时间
private void commitSession() {
HttpSessionWrapper wrappedSession = getCurrentSession();
if (wrappedSession == null) {
if (isInvalidateClientSession()) {
SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this, this.response);
}
}
else {
S session = wrappedSession.getSession();
clearRequestedSessionCache();
//保存进redis
SessionRepositoryFilter.this.sessionRepository.save(session);
String sessionId = session.getId();
if (!isRequestedSessionIdValid() || !sessionId.equals(getRequestedSessionId())) {
//将session 写入cookie
SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this, this.response, sessionId);
}
}
}
----------------------------------------------------------
//从request中获取当前的session,这样再一次请求中不会每次都去redis中查找session
private HttpSessionWrapper getCurrentSession() {
return (HttpSessionWrapper) getAttribute(CURRENT_SESSION_ATTR);
}
private void setCurrentSession(HttpSessionWrapper currentSession) {
if (currentSession == null) {
removeAttribute(CURRENT_SESSION_ATTR);
}
else {
setAttribute(CURRENT_SESSION_ATTR, currentSession);
}
}
------------------------------------------------------------
//获取session 贼鸡儿重要!!!!!
@Override
public HttpSessionWrapper getSession(boolean create) {
//首先 request中获取 当前session,没有的话从redis
中获取
HttpSessionWrapper currentSession = getCurrentSession();
if (currentSession != null) {
return currentSession;
}
//这个方法就是讲cookie转化成sessionId之后从redis中获取session,具体代码在下面
S requestedSession = getRequestedSession();
//redis中如果有域名直接返回 一般第二次的请求都是走到这里就结束了,从redis中获取session
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");
}
// 获取session的时候会带上是否创建create标识
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)"));
}
//创建redis session
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
session.setLastAccessedTime(Instant.now());
currentSession = new HttpSessionWrapper(session, getServletContext());
//将新创建的session放入request的当前session中
setCurrentSession(currentSession);
return currentSession;
}
//上面的从redis中获取session 的方法 首先通过 sessionId转换器将cookie转换成sessionId 之后再redis中找找
private S getRequestedSession() {
if (!this.requestedSessionCached) {
List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver.resolveSessionIds(this);
for (String sessionId : sessionIds) {
if (this.requestedSessionId == null) {
this.requestedSessionId = sessionId;
}
S session = SessionRepositoryFilter.this.sessionRepository.findById(sessionId);
if (session != null) {
this.requestedSession = session;
this.requestedSessionId = sessionId;
break;
}
}
this.requestedSessionCached = true;
}
return this.requestedSession;
}
复制代码
spring session 保存时序图
这里其实获取session也基本上和保存的的流程差不多所以只贴了保存的时序图

相信看了上述关键类代码的讲解,再看了时序图大家基本上对springsession的整个运行流程都有了大致的了解。
哈哈哈以上就是我分享的内容,如果有什么讲的不对的地方,及时给我留言我立刻改正,要是误导了其他的正在学的小朋友,那老夫就罪孽深重了哈哈哈哈

哈哈






















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)