美文网首页
Spring Session 内部实现原理(源码分析)

Spring Session 内部实现原理(源码分析)

作者: FX_SKY | 来源:发表于2017-04-08 18:43 被阅读1573次

    Spring Session的架构

    Spring Session定义了一组标准的接口,可以通过实现这些接口间接访问底层的数据存储。Spring Session定义了如下核心接口:Session、ExpiringSession以及SessionRepository,针对不同的数据存储,它们需要分别实现。

    • org.springframework.session.Session接口定义了session的基本功能,如设置和移除属性。这个接口并不关心底层技术,因此能够比servlet HttpSession适用于更为广泛的场景中。
    • org.springframework.session.ExpiringSession扩展了Session接口,它提供了判断session是否过期的属性。RedisSession是这个接口的一个样例实现。
    • org.springframework.session.SessionRepository定义了创建、保存、删除以及检索session的方法。将Session实例真正保存到数据存储的逻辑是在这个接口的实现中编码完成的。例如,RedisOperationsSessionRepository就是这个接口的一个实现,它会在Redis中创建、存储和删除session。

    在请求/响应周期中,客户端和服务器之间需要协商同意一种传递session id的方式。例如,如果请求是通过HTTP传递进来的,那么session可以通过HTTP cookie或HTTP Header信息与请求进行关联。

    对于HTTP协议来说,Spring Session定义了HttpSessionStrategy接口以及两个默认实现,即CookieHttpSessionStrategy和HeaderHttpSessionStrategy,其中前者使用HTTP cookie将请求与session id关联,而后者使用HTTP header将请求与session关联。

    核心思想:
    通过 org.springframework.session.web.http.SessionRepositoryFilterdoFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)对所有的请求进行拦截,使用包装(Wrapper)或者说是装饰(Decorator)模式对 request, response进行包装并重写HttpServletRequest 的 getSession方法,然后通过 filterChain向后传递。

    ** 本文基于 Spring Session 1.3.0.RELEASE 源代码分析**



    首先,看看我们在web.xml中配置:

    <!-- spring session -->
      <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>
      </filter-mapping>
    

    DelegatingFilterProxy

    DelegatingFilterProxy 顾名思义是一个Filter的代理类,其代码如下:

    
    public class DelegatingFilterProxy extends GenericFilterBean {
    
        private WebApplicationContext webApplicationContext;
    
        private String targetBeanName;
    
        private boolean targetFilterLifecycle = false;
    
        private volatile Filter delegate;
    
        private final Object delegateMonitor = new Object();
    
        public DelegatingFilterProxy() {
        }
    
        public DelegatingFilterProxy(Filter delegate) {
            Assert.notNull(delegate, "delegate Filter object must not be null");
            this.delegate = delegate;
        }
    
        public DelegatingFilterProxy(String targetBeanName) {
            this(targetBeanName, null);
        }
    
        public DelegatingFilterProxy(String targetBeanName, WebApplicationContext wac) {
            Assert.hasText(targetBeanName, "target Filter bean name must not be null or empty");
            this.setTargetBeanName(targetBeanName);
            this.webApplicationContext = wac;
            if (wac != null) {
                this.setEnvironment(wac.getEnvironment());
            }
        }
    }
    
    

    DelegatingFilterProxy 继承自 GenericFilterBean,GenericFilterBean是一个抽象类,分别实现了 Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean接口,继承关系如下图:

    DelegatingFilterProxy.png

    GenericFilterBean 主要代码如下:

    public abstract class GenericFilterBean implements
            Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean {
    
        @Override
        public void afterPropertiesSet() throws ServletException {
            initFilterBean();
        }
    
        @Override
        public final void init(FilterConfig filterConfig) throws ServletException {
            Assert.notNull(filterConfig, "FilterConfig must not be null");
            if (logger.isDebugEnabled()) {
                logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
            }
    
            this.filterConfig = filterConfig;
    
            // Set bean properties from init parameters.
            try {
                PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                String msg = "Failed to set bean properties on filter '" +
                    filterConfig.getFilterName() + "': " + ex.getMessage();
                logger.error(msg, ex);
                throw new NestedServletException(msg, ex);
            }
    
            // Let subclasses do whatever initialization they like.
            initFilterBean();
    
            if (logger.isDebugEnabled()) {
                logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
            }
        }
    
        protected void initFilterBean() throws ServletException {
        }
    }
    

    由此可见,当DelegatingFilterProxy 在执行Filter的 init 方法时,会调用 initFilterBean方法,如下:

    
        /**
         * Spring容器启动时初始化Filter
         */
        @Override
        protected void initFilterBean() throws ServletException {
            synchronized (this.delegateMonitor) {
                if (this.delegate == null) {
                    // 如果targetBeanName为null,则使用当前Filter的名称
                    if (this.targetBeanName == null) {
                        this.targetBeanName = getFilterName();
                    }
                    // Fetch Spring root application context and initialize the delegate early,
                    // if possible. If the root application context will be started after this
                    // filter proxy, we'll have to resort to lazy initialization.
                    WebApplicationContext wac = findWebApplicationContext();
                    if (wac != null) {
                        this.delegate = initDelegate(wac);
                    }
                }
            }
        }
    

    getFilterName方法继承自GenericFilterBean ,如下:

    
        public final FilterConfig getFilterConfig() {
            return this.filterConfig;
        }
    
        protected final String getFilterName() {
            return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName);
        }
    

    首先,会获取targetBeanName 的值,这里会取当前Filter的名称,也即我们在web.xml中配置的 <filter-name>属性值:springSessionRepositoryFilter,然后调用 initDelegate方法为 delegate赋值,initDelegate方法如下:

    /**
         * 初始化代理Filter
         */
        protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
            //根据getTargetBeanName() 即 springSessionRepositoryFilter去WebApplicationContext查找bean
            Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
            if (isTargetFilterLifecycle()) {
                //调用代理Filter的init方法
                delegate.init(getFilterConfig());
            }
            return delegate;
        }
    

    这里 根据 springSessionRepositoryFilter去WebApplicationContext查找Bean,找到的Filter究竟是谁呢?

    还记得我们在 applicationContext.xml中配置的 第一个bean吗?

    <!--spring session-->
        <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
            <property name="maxInactiveIntervalInSeconds" value="1800"></property>
        </bean>
    

    RedisHttpSessionConfiguration的继承关系如下:

    RedisHttpSessionConfiguration.png

    Spring Session为了减轻我们配置Bean 的负担,在 RedisHttpSessionConfiguration以及它的父类 SpringHttpSessionConfiguration中 自动生成了许多Bean,我们看看 SpringHttpSessionConfiguration的源码:

    @Configuration
    public class SpringHttpSessionConfiguration implements ApplicationContextAware {
    
        private CookieHttpSessionStrategy defaultHttpSessionStrategy = new CookieHttpSessionStrategy();
    
        private boolean usesSpringSessionRememberMeServices;
    
        private ServletContext servletContext;
    
        private CookieSerializer cookieSerializer;
    
        private HttpSessionStrategy httpSessionStrategy = this.defaultHttpSessionStrategy;
    
        private List<HttpSessionListener> httpSessionListeners = new ArrayList<HttpSessionListener>();
    
        /**
         * 我们在web.xml配置的Filter名称
         */
        @Bean
        public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(
                SessionRepository<S> sessionRepository) {
            SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<S>(
                    sessionRepository);
            sessionRepositoryFilter.setServletContext(this.servletContext);
            if (this.httpSessionStrategy instanceof MultiHttpSessionStrategy) {
                sessionRepositoryFilter.setHttpSessionStrategy(
                        (MultiHttpSessionStrategy) this.httpSessionStrategy);
            }
            else {
                sessionRepositoryFilter.setHttpSessionStrategy(this.httpSessionStrategy);
            }
            return sessionRepositoryFilter;
        }
    }
    

    看到这里明白了吧,根据 springSessionRepositoryFilter从 WebApplicationContext取到的是 org.springframework.session.web.http.SessionRepositoryFilter对象。

    接下来,我们来看看 DelegatingFilterProxy 的doFilter方法:

        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
    
            // Lazily initialize the delegate if necessary.
            Filter delegateToUse = this.delegate;
            if (delegateToUse == null) {
                synchronized (this.delegateMonitor) {
                    if (this.delegate == null) {
                        WebApplicationContext wac = findWebApplicationContext();
                        if (wac == null) {
                            throw new IllegalStateException("No WebApplicationContext found: " +
                                    "no ContextLoaderListener or DispatcherServlet registered?");
                        }
                        this.delegate = initDelegate(wac);
                    }
                    delegateToUse = this.delegate;
                }
            }
    
            // Let the delegate perform the actual doFilter operation.
            invokeDelegate(delegateToUse, request, response, filterChain);
        }
    
        /**
         * 调用代理Filter
         */
        protected void invokeDelegate(
                Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
    
            delegate.doFilter(request, response, filterChain);
        }
    

    DelegatingFilterProxy doFilter方法将每次请求都交给 delegate处理,即交给 org.springframework.session.web.http.SessionRepositoryFilter 进行处理。

    SessionRepositoryFilter

    Spring Session对HTTP的支持所依靠的是一个简单老式的Servlet Filter,借助servlet规范中标准的特性来实现Spring Session的功能。SessionRepositoryFilter就是 Servlet Filter的一个标准实现,代码如下:

    
    public class SessionRepositoryFilter<S extends ExpiringSession>
            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);
    
        private final SessionRepository<S> sessionRepository;
    
        private ServletContext servletContext;
    
        private MultiHttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy();
    
        /**
         * 构造方法
         */
        public SessionRepositoryFilter(SessionRepository<S> sessionRepository) {
            if (sessionRepository == null) {
                throw new IllegalArgumentException("sessionRepository cannot be null");
            }
            this.sessionRepository = sessionRepository;
        }
    
        /**
         * doFilter方法调用
         */
        @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, this.servletContext);
            SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
                    wrappedRequest, response);
    
            HttpServletRequest strategyRequest = this.httpSessionStrategy
                    .wrapRequest(wrappedRequest, wrappedResponse);
            HttpServletResponse strategyResponse = this.httpSessionStrategy
                    .wrapResponse(wrappedRequest, wrappedResponse);
    
            try {
                filterChain.doFilter(strategyRequest, strategyResponse);
            }
            finally {
                wrappedRequest.commitSession();
            }
        }
    }
    

    SessionRepositoryFilter 继承自OncePerRequestFilter,也是一个标准的Servlet Filter。真正的核心在于它对请求的HttpServletRequest ,HttpServletResponse 对进行包装了之后,然后调用 filterChain.doFilter(strategyRequest, strategyResponse); 往后传递,后面调用者通过 HttpServletRequest.getSession();获得session的话,得到的将会是Spring Session 提供的 HttpServletSession实例。

    其中,SessionRepositoryRequestWrapper 和 SessionRepositoryResponseWrapper是 SessionRepositoryFilter中的 内部类。

    我们可以在Controller 中进行 debug 看看我们拿到的 HttpServletRequest 和 HttpSession 到底是什么?

    @RestController
    public class EchoController {
    
        @RequestMapping(value = "/query", method = RequestMethod.GET)
        public User query(String name, HttpServletRequest request, HttpSession session){
            
            System.out.println(session);
    
            User user = new User();
            user.setId(15L);
            user.setName(name);
            user.setPassword("root");
            user.setAge(28);
    
            session.setAttribute("user", user);
    
            return user;
        }
    }
    

    在IDEA中 单步调试,结果如下:


    debug.png

    接下来,我们分析一下 SessionRepositoryRequestWrapper 中关于 getSession()实现:

    
        /**
         * HttpServletRequest getSession()实现
         */
        @Override
        public HttpSessionWrapper getSession() {
            return getSession(true);
        }
        
        @Override
        public HttpSessionWrapper getSession(boolean create) {
            HttpSessionWrapper currentSession = getCurrentSession();
            if (currentSession != null) {
                return currentSession;
            }
            //从当前请求获取sessionId
            String requestedSessionId = getRequestedSessionId();
            if (requestedSessionId != null
                    && getAttribute(INVALID_SESSION_ID_ATTR) == null) {
                S session = getSession(requestedSessionId);
                if (session != null) {
                    this.requestedSessionIdValid = true;
                    currentSession = new HttpSessionWrapper(session, getServletContext());
                    currentSession.setNew(false);
                    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 (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)"));
            }
            //为当前请求创建session
            S session = SessionRepositoryFilter.this.sessionRepository.createSession();
            session.setLastAccessedTime(System.currentTimeMillis());
            //对Spring session 进行包装(包装成HttpSession)
            currentSession = new HttpSessionWrapper(session, getServletContext());
            setCurrentSession(currentSession);
            return currentSession;
        }
        
        /**
         * 根据sessionId获取session
         */
        private S getSession(String sessionId) {
            S session = SessionRepositoryFilter.this.sessionRepository
                    .getSession(sessionId);
            if (session == null) {
                return null;
            }
            session.setLastAccessedTime(System.currentTimeMillis());
            return session;
        }
            
        /**
         * 从当前请求获取sessionId
         */
        @Override
        public String getRequestedSessionId() {
            return SessionRepositoryFilter.this.httpSessionStrategy
                    .getRequestedSessionId(this);
        }
        
        private void setCurrentSession(HttpSessionWrapper currentSession) {
            if (currentSession == null) {
                removeAttribute(CURRENT_SESSION_ATTR);
            }
            else {
                setAttribute(CURRENT_SESSION_ATTR, currentSession);
            }
        }
        /**
         * 获取当前请求session
         */
        @SuppressWarnings("unchecked")
        private HttpSessionWrapper getCurrentSession() {
            return (HttpSessionWrapper) getAttribute(CURRENT_SESSION_ATTR);
        }
    
    

    注释写的很清楚,就不啰嗦了。

    参考资料

    通过Spring Session实现新一代的Session管理

    相关文章

      网友评论

          本文标题:Spring Session 内部实现原理(源码分析)

          本文链接:https://www.haomeiwen.com/subject/luxrattx.html