Spring Session 源码分析(1)——springSessionRepositoryFilter

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

原文发布于: http://blog.ztgreat.cn/article/40

#Tomcat Session
对于session 是一个老生畅谈的话题了,Session管理是JavaEE容器比较重要的一部分,
Tomcat中主要由每个context容器内的一个Manager对象来管理session。对于这个manager对象的实现,可以根据tomcat提供的接口或基类来自己定制,同时,tomcat也提供了标准实现。
在每个context对象,即web app都具有一个独立的manager对象。通过server.xml可以配置定制化的manager,也可以不配置。不管怎样,在生成context对象时,都会生成一个manager对象。缺省的是StandardManager类,在Tomcat 中,Session 是保存到一个ConcurrentHashMap 中的。

    /**
     * Minimal implementation of the <b>Manager</b> interface that supports
     * no session persistence or distributable capabilities.  This class may
     * be subclassed to create more sophisticated Manager implementations.
     *
     * @author Craig R. McClanahan
     */
    public abstract class ManagerBase extends LifecycleMBeanBase implements Manager {
        /**
         * The set of currently active Sessions for this Manager, keyed by
         * session identifier.
         */
        protected Map<String, Session> sessions = new ConcurrentHashMap<>();
        ...
     }

Session 自定义化,可以实现标准servlet的session接口:

    javax.servlet.http.HttpSession

Tomcat也提供了标准的session实现:

    org.apache.catalina.session.StandardSession
    /**
     * Standard implementation of the <b>Session</b> interface.  This object is
     * serializable, so that it can be stored in persistent storage or transferred
     * to a different JVM for distributable session support.
     * <p>
     * <b>IMPLEMENTATION NOTE</b>:  An instance of this class represents both the
     * internal (Session) and application level (HttpSession) view of the session.
     * However, because the class itself is not declared public, Java logic outside
     * of the <code>org.apache.catalina.session</code> package cannot cast an
     * HttpSession view of this instance back to a Session view.
     * <p>
     * <b>IMPLEMENTATION NOTE</b>:  If you add fields to this class, you must
     * make sure that you carry them over in the read/writeObject methods so
     * that this class is properly serialized.
     *
     * @author Craig R. McClanahan
     * @author Sean Legassick
     * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
     */
    public class StandardSession implements HttpSession, Session, Serializable {
        /**
         * Construct a new Session associated with the specified Manager.
         *
         * @param manager The manager with which this Session is associated
         */
        public StandardSession(Manager manager) {

            super();
            this.manager = manager;

            // Initialize access count
            if (ACTIVITY_CHECK) {
                accessCount = new AtomicInteger();
            }
        }
        ...
     }

今天的目的不是讨论Tomcat 对session的管理,这里只是做个引子,方便感兴趣的朋友深入了解,今天我们主要看Spring 如果接管 Session的,并且使用Redis作为Session的存储容器。

#Spring Session

这篇文章并不是深入剖析Spring Session的结构,主要是一起来看看Spring 是如何接管Session的,从中学习一些架构思想。

##Spring Session 基于XML的配置

注意:下面的配置是核心配置,而非全部配置

spring 配置文件:

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

web.xml:

    <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的代理类,DelegatingFilterProxy 继承自 GenericFilterBean,GenericFilterBean是一个抽象类,分别实现了 Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean接口,继承关系如下图:

20191017100213\_1.png

对于Filter 来说,最重要肯定就是初始化(init)和doFilter 方法了,初始化方法在父类GenericFilterBean 中:

    /**
     * Standard way of initializing this filter.
     * Map config parameters onto bean properties of this filter, and
     * invoke subclass initialization.
     * @param filterConfig the configuration for this filter
     * @throws ServletException if bean properties are invalid (or required
     * properties are missing), or if subclass initialization fails.
     * @see #initFilterBean
     */
    @Override
    public final void init(FilterConfig filterConfig) throws ServletException {
        ...//省略部分代码

        // Let subclasses do whatever initialization they like.
        initFilterBean();  //注意这里

        if (logger.isDebugEnabled()) {
            logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
        }
    }

initFilterBean在子类中实现,也就是说当DelegatingFilterProxy 在执行Filter的 init 方法时,会调用 initFilterBean方法,如下:

        @Override
        protected void initFilterBean() throws ServletException {
            synchronized (this.delegateMonitor) {
                // delegate 为实际的Filter
                if (this.delegate == null) {
                    // If no target bean name specified, use filter name.
                    if (this.targetBeanName == null) {
                        //这里的targetBeanName  便是我们web.xml 中配置的springSessionRepositoryFilter
                        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) {
                        //初始化Filter
                        this.delegate = initDelegate(wac);
                    }
                }
            }
        }

delegate 为真正的Filter,通过web.xml 中配置的Filter 名字(即:springSessionRepositoryFilter)来获取这个Filter,我们继续往下看initDelegate 这个方法:

    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }

通过上下文,获取到这个Filter Bean,然后初始化这个Filter。但是我们似乎没有手动配置这个Bean,那么这个切入点就是我们开始在spring 配置文件中声明的 RedisHttpSessionConfiguration 这个bean了。
20191017100213\_2.png

##SessionRepositoryFilter
Spring Session 在 RedisHttpSessionConfiguration以及它的父类 SpringHttpSessionConfiguration中 自动生成了许多Bean,这里我只列举了springSessionRepositoryFilter 这个Bean,而这个Bean 是SessionRepositoryFilter 类型的。

    @Configuration
    public class SpringHttpSessionConfiguration {

    private CookieHttpSessionStrategy defaultHttpSessionStrategy = new CookieHttpSessionStrategy();

    private HttpSessionStrategy httpSessionStrategy = this.defaultHttpSessionStrategy;

    @Bean
    public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(
            SessionRepository<S> sessionRepository) {
            //传入 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;
    }

##RedisOperationsSessionRepository
现在我们知道,DelegatingFilterProxy 中真正的Filter是SessionRepositoryFilter,在生成SessionRepositoryFilter 是传入了sessionRepository,通过名字我们大概知道,这个应该是具体操作session的类,而这个类又具体是什么呢?,看下面的代码:

    @Bean
    public RedisOperationsSessionRepository sessionRepository(
            @Qualifier("sessionRedisTemplate") RedisOperations<Object, Object> sessionRedisTemplate,
            ApplicationEventPublisher applicationEventPublisher) {
        RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(
                sessionRedisTemplate);
        sessionRepository.setApplicationEventPublisher(applicationEventPublisher);
        sessionRepository
                .setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
        if (this.defaultRedisSerializer != null) {
            sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
        }

        String redisNamespace = getRedisNamespace();
        if (StringUtils.hasText(redisNamespace)) {
            sessionRepository.setRedisKeyNamespace(redisNamespace);
        }

        sessionRepository.setRedisFlushMode(this.redisFlushMode);
        return sessionRepository;
    }

到这里我们知道,这个sessionRepository 具体就是:RedisOperationsSessionRepository ,这个类就是实际通过redis 操作session的类,这里就不展开了,后面会简单看一下。

好了,现在回到 DelegatingFilterProxy -> initFilterBean 方法,具体的Filter 已经找到了,这个Filter 就是SessionRepositoryFilter,那么这个Filter 又是在什么时候生效的呢?,接下来我们看 DelegatingFilterProxy 的doFilter 方法。

    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) {
                delegateToUse = this.delegate;
                if (delegateToUse == null) {
                    WebApplicationContext wac = findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: " +
                                "no ContextLoaderListener or DispatcherServlet registered?");
                    }
                    delegateToUse = initDelegate(wac);
                }
                this.delegate = delegateToUse;
            }
        }

        // Let the delegate perform the actual doFilter operation.
        invokeDelegate(delegateToUse, request, response, filterChain);
    }

注意 invokeDelegate 方法,进去看看:

    protected void invokeDelegate(
            Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        //调用 delegate的 doFilter 方法
        delegate.doFilter(request, response, filterChain);
    }

这里我们大概知道了,DelegatingFilterProxy 实际上是委托给delegate 的,而这里的delegate 是SessionRepositoryFilter,现在我们又回到 SessionRepositoryFilter看看:

20191017100213\_3.png

SessionRepositoryFilter 没有重写 doFilter 方法,因此查看父类:OncePerRequestFilter,通过名字我们知道,这个Filter 只执行一次

    OncePerRequestFilter -> doFilter:
    /**
     * This {@code doFilter} implementation stores a request attribute for
     * "already filtered", proceeding without filtering again if the attribute is already
     * there.
     */
    public final void doFilter(ServletRequest request, ServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {

        if (!(request instanceof HttpServletRequest)
                || !(response instanceof HttpServletResponse)) {
            throw new ServletException(
                    "OncePerRequestFilter just supports HTTP requests");
        }
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        //查看是否已经过滤了
        boolean hasAlreadyFilteredAttribute = request
                .getAttribute(this.alreadyFilteredAttributeName) != null;

        if (hasAlreadyFilteredAttribute) {

            // Proceed without invoking this filter...
            filterChain.doFilter(request, response);
        }
        else {
            // Do invoke this filter...
            //加入已经过滤标志
            request.setAttribute(this.alreadyFilteredAttributeName, Boolean.TRUE);
            try {
                //调用 doFilterInternal
                doFilterInternal(httpRequest, httpResponse, filterChain);
            }
            finally {
                // Remove the "already filtered" request attribute for this request.
                request.removeAttribute(this.alreadyFilteredAttributeName);
            }
        }
    }

在该方法中,会检查该请求是否已经被过滤了,如果过滤过了那么执行下一个Filter,否则放入已过滤标识,然后执行Filter 功能,doFilterInternal 在SessionRepositoryFilter 被重写

    SessionRepositoryFilter -> doFilterInternal:
    @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);
        //包装 request,response
        HttpServletRequest strategyRequest = this.httpSessionStrategy
                .wrapRequest(wrappedRequest, wrappedResponse);
        HttpServletResponse strategyResponse = this.httpSessionStrategy
                .wrapResponse(wrappedRequest, wrappedResponse);

        try {
            filterChain.doFilter(strategyRequest, strategyResponse);
        }
        finally {
            //更新session
            wrappedRequest.commitSession();
        }
    }

Spring 接管Tomcat 的session 主要在这里可以看出来,Spring会包装HttpServletRequest ,HttpServletResponse ,当执行了这个Filter后,将包装了 request,response 专递到后面,这样后面所使用的都是spring 包装过的,这样在获取session的接口,就被spring 所控制了,当由服务器返回给用户是,调用commitSession,更新session。
具接下来我们看一下SessionRepositoryRequestWrapper 这个类:
20191017100213\_4.png

    private S getSession(String sessionId) {
        //通过sessionRepository 获取session,在redis 中 这里的 sessionRepository 是RedisOperationsSessionRepository 
        S session = SessionRepositoryFilter.this.sessionRepository
                .getSession(sessionId);
        if (session == null) {
            return null;
        }
        session.setLastAccessedTime(System.currentTimeMillis());
        return session;
    }

    // 重写 父类 getSession 方法
    @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;
                //对Spring session 进行包装(包装成HttpSession)
                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
                setAttribute(INVALID_SESSION_ID_ATTR, "true");
            }
        }
        if (!create) {
            return null;
        }
        S session = SessionRepositoryFilter.this.sessionRepository.createSession();
        session.setLastAccessedTime(System.currentTimeMillis());
        //对Spring session 进行包装(包装成HttpSession)
        currentSession = new HttpSessionWrapper(session, getServletContext());
        setCurrentSession(currentSession);
        return currentSession;
    }

到这里应该就很清楚了,对HttpServletRequest 进行包装,然后重写对Session操作的接口,内部调用 SessionRepository 的实现类来对session 进行操作,ok,这下终于明白Spring Session 是如何控制session的了,最后进行总结一下:
当我们配置 DelegatingFilterProxy 时,会配置 filter-name:springSessionRepositoryFilter,当我们配置 RedisHttpSessionConfiguration 这个bean时,这个Filter 则由Spring 给我生成,而这个Filter 实际是 :SessionRepositoryFilter, 当有请求到达时,DelegatingFilterProxy 委托给 SessionRepositoryFilter,而它又将HttpServletRequest,HttpServletResponse 进行一定的包装,重写对session操作的接口,然后将包装好的request,response 传递到后续的Filter中,完成了对Session的拦截操作,后续应用操作的Session 都是Spring Session 包装后的Session。

##参考
Spring Session 内部实现原理(源码分析)
通过Spring Session实现新一代的Session管理


来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring Session 源码分析(1)——springSessionRepositoryFilter

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏