1: Introduction to spring-session
1. Introduction
Session has always been a difficult problem that we need to solve when doing clusters. In the past, we could solve it from servlet containers, such as tomcat-redis-session-manager and memcached-session-manager provided by open source servlet container-tomcat.
Or do ip_hash through load balancing such as nginx and routing to a specific server..
But both methods have disadvantages.
spring-session is a project under spring. It replaces the httpSession implemented by the servlet container with spring-session, focusing on solving session management problems. It can be integrated into our applications quickly and seamlessly.
2. Support functions
1) Easily store sessions in third-party storage containers. The framework provides redis, jvm map, mongo, gemfire, hazelcast, jdbc and other containers for storing sessions.
2) The same browser and the same website support multiple session problems.
3) RestfulAPI, does not rely on cookies. The jessionID can be passed through the header
4) Combining WebSocket and spring-session to synchronize life cycle management.
3. Integration method
The integration method is very simple, just look at the samples and guide on the official website. http://docs.spring.io/spring-session/docs/1.3.0.RELEASE/reference/html5/
It is mainly divided into the following integration steps:
1) Introduce dependency jar package
2) Annotation method or xml method to configure storage methods for specific storage containers, such as redis's xml configuration method
<context:annotation-config/> /** Initialize all spring-session preparations and put springSessionFilter into IOC **/<beanclass="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/> /** This is the link pool for the storage container**/ <beanclass="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
3) Configure web.xml in xml method, configure springSessionFilter into filter chain
<filter> <filter-name>springSessionRepositoryFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSessionRepositoryFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher><dispatcher>ERROR</dispatcher> </filter-mapping>
Two: Internal analysis of spring-session framework
1. Frame high-level abstract structure diagram
2.Spring-session rewrites servlet request and redis storage related issues
The general principle of spring-session seamlessly replaces the request of application server is:
1. Customize a Filter and implement the doFilter method
2. Inherit the HttpServletRequestWrapper and HttpServletResponseWrapper classes, and override the getSession and other related methods (the relevant session storage container operation class is called in these methods).
3. In the doFilter step in the first step, new the request and response classes in the second step. and pass them separately to the filter chain
4. Configure the filter to the first position of the filter chain
/** This class is the 1.30 source code of spring-session, and it is also the key class that implements the first to third steps above**/public class SessionRepositoryFilter<S extends ExpiringSession> extends OncePerRequestFilter { /** session storage container interface, redis, mongoDB, genfire and other databases all implement this interface**/ private final SessionRepository<S> sessionRepository; private ServletContext servletContext; /** sessionID delivery interface. Currently spring-session comes with two implementation classes 1.cookie method: CookieHttpSessionStrategy 2.http header method: HeaderHttpSessionStrategy Of course, we can also customize other methods. **/ private MultiHttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy(); public SessionRepositoryFilter(SessionRepository<S> sessionRepository) { if (sessionRepository == null) { throw new IllegalArgumentException("sessionRepository cannot be null"); } this.sessionRepository = sessionRepository; } public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) { if (httpSessionStrategy == null) { throw new IllegalArgumentException("httpSessionStrategy cannot be null"); } /** Through the previous spring-session function introduction, we know that spring-session can support multiple sessions in a single browser, which is achieved through the MultiHttpSessionStrategyAdapter. Each browser has a sessionID, but this sessionID has multiple aliases (according to the browser's tab). For example: alias 1 sessionID Alias 2 sessionID ... And this alias is passed through the url, which is the principle of multiple sessions by a single browser **/ this.httpSessionStrategy = new MultiHttpSessionStrategyAdapter( httpSessionStrategy); } public void setHttpSessionStrategy(MultiHttpSessionStrategy httpSessionStrategy) { if (httpSessionStrategy == null) { throw new IllegalArgumentException("httpSessionStrategy cannot be null"); } this.httpSessionStrategy = httpSessionStrategy; } /** This method is equivalent to rewriting doFilter, but spring-session has another layer of encapsulation. Create a custom request and response in this method, and then pass it to the filter chain filterChain **/ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository); /** ServletRequest rewritten by spring-session. This class inherits HttpServletRequestWrapper **/ SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper( request, response, this.servletContext); SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper( wrappedRequest, response); HttpServletRequest strategyRequest = this.httpSessionStrategy.wrapRequest(wrappedRequest, wrappedResponse); HttpServletResponse strategyResponse = this.httpSessionStrategy .wrapResponse(wrappedRequest, wrappedResponse); try { /** Pass the custom request and response to the chain. Imagine if the spring-sessionFilter is located at the first of the filter chain, then are the subsequent Filters, as well as the request and response obtained by reaching the last control layer, we are customizing it? **/ filterChain.doFilter(strategyRequest, strategyResponse); } finally { wrappedRequest.commitSession(); } } public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } /** This is the rewritten class of Servlet response*/ private final class SessionRepositoryResponseWrapper extends OnCommittedResponseWrapper { private final SessionRepositoryRequestWrapper request; SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request, HttpServletResponse response) { super(response); if (request == null) { throw new IllegalArgumentException("request cannot be null"); } this.request = request; } /** This step is to persist the session to the storage container. We may call the operation method of the session multiple times in a control layer. If each operation of the session is persisted to the storage container, it will definitely have a performance impact. For example, redis, so we can execute the entire control layer, and the response returns information to the browser, and the session will be persisted only when the response returns the information to the browser. **/ @Override protected void onResponseCommitted() { this.request.commitSession(); } } /** The request rewrite class of spring-session is almost the most important rewrite class. It rewritten methods such as getSession, Session and other methods as well as classes */ private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper { private Boolean requestedSessionIdValid; private boolean requestedSessionInvalidated; private final HttpServletResponse response; private final ServletContext servletContext; private SessionRepositoryRequestWrapper(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) { super(request); this.response = response; this.servletContext = servletContext; } /** * Uses the HttpSessionStrategy to write the session id to the response and * persist the Session. */ private void commitSession() { HttpSessionWrapper wrappedSession = getCurrentSession(); if (wrappedSession == null) { // session expires, delete cookies or header if (isInvalidateClientSession()) { SessionRepositoryFilter.this.httpSessionStrategy .onInvalidateSession(this, this.response); } } else { S session = wrappedSession.getSession(); SessionRepositoryFilter.this.sessionRepository.save(session); if (!isRequestedSessionIdValid() || !session.getId().equals(getRequestedSessionId())) { // Write the cookie or header back to the browser to save SessionRepositoryFilter.this.httpSessionStrategy.onNewSession(session, this, this.response); } } } @SuppressWarnings("unchecked") 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); } } @SuppressWarnings("unused") public String changeSessionId() { HttpSession session = getSession(false); if (session == null) { throw new IllegalStateException( "Cannot change session ID. There is no session associated with this request."); } // eagerly get session attributes in case implementation lazy loads them Map<String, Object> attrs = new HashMap<String, Object>(); Enumeration<String> iAttrNames = session.getAttributeNames(); while (iAttrNames.hasMoreElements()) { String attrName = iAttrNames.nextElement(); Object value = session.getAttribute(attrName); attrs.put(attrName, value); } SessionRepositoryFilter.this.sessionRepository.delete(session.getId()); HttpSessionWrapper original = getCurrentSession(); setCurrentSession(null); HttpSessionWrapper newSession = getSession(); original.setSession(newSession.getSession()); newSession.setMaxInactiveInterval(session.getMaxInactiveInterval()); for (Map.Entry<String, Object> attr : attrs.entrySet()) { String attrName = attr.getKey(); Object attrValue = attr.getValue(); newSession.setAttribute(attrName, attrValue); } return newSession.getId(); } // Determine whether the session is valid @Override public boolean isRequestedSessionIdValid() { if (this.requestedSessionIdValid == null) { String sessionId = getRequestedSessionId(); S session = sessionId == null ? null : getSession(sessionId); return isRequestedSessionIdValid(session); } return this.requestedSessionIdValid; } private boolean isRequestedSessionIdValid(S session) { if (this.requestedSessionIdValid == null) { this.requestedSessionIdValid = session != null; } return this.requestedSessionIdValid; } private boolean isInvalidateClientSession() { return getCurrentSession() == null && this.requestedSessionInvalidated; } private S getSession(String sessionId) { // Get session from the session storage container based on sessionID S session = SessionRepositoryFilter.this.sessionRepository .getSession(sessionId); if (session == null) { return null; } // Set the last access time of the session to prevent the session from expired session.setLastAccessedTime(System.currentTimeMillis()); return session; } /** Is this method very familiar? There is also a getSession() below to be more familiar. That's right, just re-get the session method here**/ @Override public HttpSessionWrapper getSession(boolean create) { //Fastly get session, which can be understood as a relationship between first-level cache and second-level cache HttpSessionWrapper currentSession = getCurrentSession(); if (currentSession != null) { return currentSession; } //Get sessionID from httpSessionStret requestedSessionId = getRequestedSessionId(); if (requestedSessionId != null && getAttribute(INVALID_SESSION_ID_ATTR) == null) { //Get session from the storage container and set the current initialization attribute S session = getSession(requestedSessionId); if (session != null) { this.requestedSessionIdValid = true; currentSession = new HttpSessionWrapper(session, getServletContext()); currentSession.setNew(false); setCurrentSession(currentSession); return currentSession; } else { 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 (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)")); } // If the browser or other http visitor is accessing the server for the first time, create a new session S session for him = SessionRepositoryFilter.this.sessionRepository.createSession(); session.setLastAccessedTime(System.currentTimeMillis()); currentSession = new HttpSessionWrapper(session, getServletContext()); setCurrentSession(currentSession); return currentSession; } @Override public ServletContext getServletContext() { if (this.servletContext != null) { return this.servletContext; } // Servlet 3.0+ return super.getServletContext(); } @Override public HttpSessionWrapper getSession() { return getSession(true); } @Override public String getRequestedSessionId() { return SessionRepositoryFilter.this.httpSessionStrategy .getRequestedSessionId(this); } /** Rewrite class of HttpSession*/ private final class HttpSessionWrapper extends ExpiringSessionHttpSession<S> { HttpSessionWrapper(S session, ServletContext servletContext) { super(session, servletContext); } @Override public void invalidate() { super.invalidate(); SessionRepositoryRequestWrapper.this.requestedSessionInvalidated = true; setCurrentSession(null); SessionRepositoryFilter.this.sessionRepository.delete(getId()); } } }}Summarize
The above is all the content of this article about the introduction of spring-session and the source code analysis of implementation principles. I hope it will be helpful to everyone. Interested friends can continue to refer to other related topics on this site. If there are any shortcomings, please leave a message to point it out!