Spring Session has always been used in the project, but it was only used in the past without in-depth understanding of its principle. This time, the project happened to be not very busy and I studied its principle carefully

Use of Spring Session

It’s very simple to use. There are a lot of tutorials on the Internet. Here is a brief introduction of JAR packages

 <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>
Copy the code

annotations

@ EnableRedisHttpSession (redisNamespace = "XXXX")Copy the code

It can change the prefix of your session stored in Redis

The source code parsing

Let’s focus on a few classes

EnableRedisHttpSession / / comment RedisHttpSessionConfiguration / / load the redis configuration class RedisIndexedSessionRepository / / spring Session redis action class SpringHttpSessionConfiguration / / initialization springsession filter configuration SessionRepositoryFilter / / spring session Filter, Here to get the session to create the session save session SessionRepositoryRequestWrapper CookieHttpSessionIdResolver / / cookie into sessionId converter // Rewrites the request getSession method, which will be retrieved from RedisCopy the code

So by going through these classes you can basically understand the whole springsession process how does a session go into redis and how does it go out of redis

EnableRedisHttpSession

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(RedisHttpSessionConfiguration.class) @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 in redis expiry time 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 */ // 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;Copy the code

From the above code, we should have a general idea of this. In the project, we often need to set the session expiration time, and the save prefix in different services redis is also different. Then we should know which class we should analyze next. That’s right is that he introduced RedisHttpSessionConfiguration this class

RedisHttpSessionConfiguration

In this class you can understand that it is mainly configured with some redis related stuff

/ / main is session stored in the shelf life of redis sessionRepository @ 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 listener, General listening is session in redis is deleted or reached the expiry time @ 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; }Copy the code

RedisIndexedSessionRepository

This class is mainly related to redis operations, mainly we can focus on two methods: 1 save session 2 obtain session through sessionId

	@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);
	}
Copy the code

SpringHttpSessionConfiguration

@Bean public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter( SessionRepository<S> sessionRepository) { SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(sessionRepository); sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver); //sessionId converter return sessionRepositoryFilter; }Copy the code

The sessionRepositoryFilter is set to a new HttpSessionIdResolver. This is the class that generates the sessionId. Look at the default converter in the code

@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;

Copy the code

By the above code can clearly see the default sessionId converter is CookieHttpSessionIdResolver, then look at the sessionId is how to generate

CookieHttpSessionIdResolver

Override public List<String> resolveSessionIds(HttpServletRequest Request) {return this.cookieSerializer.readCookieValues(request); 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; }Copy the code

ResolveSessionIds get sessionids by readCookieValues You can see that the method of obtaining the sessionId is actually base64 transcoding the value in the cookie.

SessionRepositoryFilter

Next comes the spring Session filter, which is where everything starts

@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 {/ / in this method the session will be stored in redis wrappedRequest.com mitSession (); }}Copy the code

Above is the performance of the filter specific logic, here we have a look at the first SessionRepositoryRequestWrapper this class, the class focuses on is getSession method, he rewrote the getSession method of the request, Instead of getting it from Redis

SessionRepositoryRequestWrapper

Write down a few key methods

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. */ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / save the session into 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(); / / save into redis SessionRepositoryFilter. This. SessionRepository. Save (session); String sessionId = session.getId(); if (! isRequestedSessionIdValid() || ! Sessionid.equals (getRequestedSessionId())) {// Write session to cookie SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this, this.response, sessionId); }}} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / from the request for the current session, so that once again the request not every time to find the session redis 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); }} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / get the session thief chicken son important!!!!!!!!!!! @override public HttpSessionWrapper getSession(Boolean create) {// Get the current session from request, if not from Redis HttpSessionWrapper currentSession = getCurrentSession(); if (currentSession ! = null) { return currentSession; SessionId = getRequestedSession(); sessionId = sessionId; 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"); } // 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)")); } / / create a redis session S session = SessionRepositoryFilter. Enclosing sessionRepository. CreateSession (); session.setLastAccessedTime(Instant.now()); currentSession = new HttpSessionWrapper(session, getServletContext()); // add the newly created session to the request's currentSession setCurrentSession(currentSession); return currentSession; } 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; }Copy the code

Spring Session stores sequence diagrams

In fact, the process of retrieving the session is basically the same as that of saving, so only the saved sequence diagram is pasted

I believe that by looking at the key class code described above, and looking at the sequence diagram, you basically have an overview of the whole process of springsession.

Hahaha the above is the content I share, if there is something wrong, give me a message and I will correct it immediately, if it misleads the other children who are learning, then the old man will be guilty hahaha

Ha ha