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

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中配置:


  
    springSessionRepositoryFilter
    org.springframework.web.filter.DelegatingFilterProxy
  
  
    springSessionRepositoryFilter
    /*
  

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接口,继承关系如下图:

Spring Session 内部实现原理(源码分析)_第1张图片
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中配置的 属性值: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吗?


    
        
    

RedisHttpSessionConfiguration的继承关系如下:

Spring Session 内部实现原理(源码分析)_第2张图片
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 httpSessionListeners = new ArrayList();

    /**
     * 我们在web.xml配置的Filter名称
     */
    @Bean
    public  SessionRepositoryFilter springSessionRepositoryFilter(
            SessionRepository sessionRepository) {
        SessionRepositoryFilter sessionRepositoryFilter = new SessionRepositoryFilter(
                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
        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 sessionRepository;

    private ServletContext servletContext;

    private MultiHttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy();

    /**
     * 构造方法
     */
    public SessionRepositoryFilter(SessionRepository 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中 单步调试,结果如下:


Spring Session 内部实现原理(源码分析)_第3张图片
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 内部实现原理(源码分析))