Spring Session 內(nèi)部實(shí)現(xiàn)原理(源碼分析)

Spring Session的架構(gòu)

Spring Session定義了一組標(biāo)準(zhǔn)的接口,可以通過實(shí)現(xiàn)這些接口間接訪問底層的數(shù)據(jù)存儲(chǔ)。Spring Session定義了如下核心接口:Session、ExpiringSession以及SessionRepository,針對不同的數(shù)據(jù)存儲(chǔ),它們需要分別實(shí)現(xiàn)。

  • org.springframework.session.Session接口定義了session的基本功能,如設(shè)置和移除屬性。這個(gè)接口并不關(guān)心底層技術(shù),因此能夠比servlet HttpSession適用于更為廣泛的場景中。
  • org.springframework.session.ExpiringSession擴(kuò)展了Session接口,它提供了判斷session是否過期的屬性。RedisSession是這個(gè)接口的一個(gè)樣例實(shí)現(xiàn)。
  • org.springframework.session.SessionRepository定義了創(chuàng)建、保存、刪除以及檢索session的方法。將Session實(shí)例真正保存到數(shù)據(jù)存儲(chǔ)的邏輯是在這個(gè)接口的實(shí)現(xiàn)中編碼完成的。例如,RedisOperationsSessionRepository就是這個(gè)接口的一個(gè)實(shí)現(xiàn),它會(huì)在Redis中創(chuàng)建、存儲(chǔ)和刪除session。

在請求/響應(yīng)周期中,客戶端和服務(wù)器之間需要協(xié)商同意一種傳遞session id的方式。例如,如果請求是通過HTTP傳遞進(jìn)來的,那么session可以通過HTTP cookie或HTTP Header信息與請求進(jìn)行關(guān)聯(lián)。

對于HTTP協(xié)議來說,Spring Session定義了HttpSessionStrategy接口以及兩個(gè)默認(rèn)實(shí)現(xiàn),即CookieHttpSessionStrategy和HeaderHttpSessionStrategy,其中前者使用HTTP cookie將請求與session id關(guān)聯(lián),而后者使用HTTP header將請求與session關(guān)聯(lián)。

核心思想:
通過 org.springframework.session.web.http.SessionRepositoryFilterdoFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)對所有的請求進(jìn)行攔截,使用包裝(Wrapper)或者說是裝飾(Decorator)模式對 request, response進(jìn)行包裝并重寫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 顧名思義是一個(gè)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是一個(gè)抽象類,分別實(shí)現(xiàn)了 Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean接口,繼承關(guān)系如下圖:

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 {
    }
}

由此可見,當(dāng)DelegatingFilterProxy 在執(zhí)行Filter的 init 方法時(shí),會(huì)調(diào)用 initFilterBean方法,如下:


    /**
     * Spring容器啟動(dòng)時(shí)初始化Filter
     */
    @Override
    protected void initFilterBean() throws ServletException {
        synchronized (this.delegateMonitor) {
            if (this.delegate == null) {
                // 如果targetBeanName為null,則使用當(dāng)前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);
    }

首先,會(huì)獲取targetBeanName 的值,這里會(huì)取當(dāng)前Filter的名稱,也即我們在web.xml中配置的 <filter-name>屬性值:springSessionRepositoryFilter,然后調(diào)用 initDelegate方法為 delegate賦值,initDelegate方法如下:

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

這里 根據(jù) springSessionRepositoryFilter去WebApplicationContext查找Bean,找到的Filter究竟是誰呢?

還記得我們在 applicationContext.xml中配置的 第一個(gè)bean嗎?

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

RedisHttpSessionConfiguration的繼承關(guān)系如下:

RedisHttpSessionConfiguration.png

Spring Session為了減輕我們配置Bean 的負(fù)擔(dān),在 RedisHttpSessionConfiguration以及它的父類 SpringHttpSessionConfiguration中 自動(dòng)生成了許多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;
    }
}

看到這里明白了吧,根據(jù) 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);
    }

    /**
     * 調(diào)用代理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 進(jìn)行處理。

SessionRepositoryFilter

Spring Session對HTTP的支持所依靠的是一個(gè)簡單老式的Servlet Filter,借助servlet規(guī)范中標(biāo)準(zhǔn)的特性來實(shí)現(xiàn)Spring Session的功能。SessionRepositoryFilter就是 Servlet Filter的一個(gè)標(biāo)準(zhǔn)實(shí)現(xiàn),代碼如下:


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();

    /**
     * 構(gòu)造方法
     */
    public SessionRepositoryFilter(SessionRepository<S> sessionRepository) {
        if (sessionRepository == null) {
            throw new IllegalArgumentException("sessionRepository cannot be null");
        }
        this.sessionRepository = sessionRepository;
    }

    /**
     * doFilter方法調(diào)用
     */
    @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 繼承自O(shè)ncePerRequestFilter,也是一個(gè)標(biāo)準(zhǔn)的Servlet Filter。真正的核心在于它對請求的HttpServletRequest ,HttpServletResponse 對進(jìn)行包裝了之后,然后調(diào)用 filterChain.doFilter(strategyRequest, strategyResponse); 往后傳遞,后面調(diào)用者通過 HttpServletRequest.getSession();獲得session的話,得到的將會(huì)是Spring Session 提供的 HttpServletSession實(shí)例。

其中,SessionRepositoryRequestWrapper 和 SessionRepositoryResponseWrapper是 SessionRepositoryFilter中的 內(nèi)部類。

我們可以在Controller 中進(jìn)行 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中 單步調(diào)試,結(jié)果如下:


debug.png

接下來,我們分析一下 SessionRepositoryRequestWrapper 中關(guān)于 getSession()實(shí)現(xiàn):


    /**
     * HttpServletRequest getSession()實(shí)現(xiàn)
     */
    @Override
    public HttpSessionWrapper getSession() {
        return getSession(true);
    }
    
    @Override
    public HttpSessionWrapper getSession(boolean create) {
        HttpSessionWrapper currentSession = getCurrentSession();
        if (currentSession != null) {
            return currentSession;
        }
        //從當(dāng)前請求獲取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)"));
        }
        //為當(dāng)前請求創(chuàng)建session
        S session = SessionRepositoryFilter.this.sessionRepository.createSession();
        session.setLastAccessedTime(System.currentTimeMillis());
        //對Spring session 進(jìn)行包裝(包裝成HttpSession)
        currentSession = new HttpSessionWrapper(session, getServletContext());
        setCurrentSession(currentSession);
        return currentSession;
    }
    
    /**
     * 根據(jù)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;
    }
        
    /**
     * 從當(dāng)前請求獲取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);
        }
    }
    /**
     * 獲取當(dāng)前請求session
     */
    @SuppressWarnings("unchecked")
    private HttpSessionWrapper getCurrentSession() {
        return (HttpSessionWrapper) getAttribute(CURRENT_SESSION_ATTR);
    }

注釋寫的很清楚,就不啰嗦了。

參考資料

通過Spring Session實(shí)現(xiàn)新一代的Session管理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,678評論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,283評論 6 342
  • 這部分主要是與Java Web和Web Service相關(guān)的面試題。 96、闡述Servlet和CGI的區(qū)別? 答...
    雜貨鋪老板閱讀 1,504評論 0 10
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,849評論 11 349
  • 本文包括:1、Filter簡介2、Filter是如何實(shí)現(xiàn)攔截的?3、Filter開發(fā)入門4、Filter的生命周期...
    廖少少閱讀 7,526評論 3 56

友情鏈接更多精彩內(nèi)容