Spring-Session实现Session共享实现原理以及源码解析

知其然,还要知其所以然 !

本篇介绍Spring-Session的整个实现的原理。以及对核心的源码进行简单的介绍!

实现原理介绍

实现原理这里简单说明描述:

就是当Web服务器接收到http请求后,当请求进入对应的Filter进行过滤,将原本需要由web服务器创建会话的过程转交给Spring-Session进行创建,本来创建的会话保存在Web服务器内存中,通过Spring-Session创建的会话信息可以保存第三方的服务中,如:redis,mysql等。Web服务器之间通过连接第三方服务来共享数据,实现Session共享!

Spring-Session实现Session共享实现原理以及源码解析_第1张图片
实现原理结构草图

整个实现流程和源码详细介绍

本次源码介绍基于上一篇内容,并且在保存Session的时候只会分析使用JedisConnectionFactory实现的RedisConnectionFactory !

1.SessionRepositoryFilter和JedisConnectionFactory注册过程

流程:

Spring-Session实现Session共享实现原理以及源码解析_第2张图片
SessionRepositoryFilter和JedisConnectionFactory注册过程

说明:

1.、启动WEB项目的时候,会读取web.xml,读取顺序content-param --> listener --> filter --> servlet

2.、ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息

3、初始化根web应用程序上下文。

4、SpringHttpSessionConfiguration注册 springSessionRepositoryFilter :bean,RedisHttpSessionConfiguration 注册 sessionRedisTemplate : bean  和 sessionRepository : bean

45、配置文件配置JedisConnectionFactory implements RedisConnectionFactory ,创建 jedisConnectionFactory bean

代码分析如下:

  1. web.xml ,加载了xml配置文件,并初始化web应用上下文
  
    contextConfigLocation
    classpath*:spring/*xml
  


  
    org.springframework.web.context.ContextLoaderListener
  
  

2.application-session.xml中,配置 RedisHttpSessionConfiguration的bean和JedisConnectionFactory的bean,web应用初始化加载bean!


    

    
    
    
        
        

        
        
    
    
    /**
     * 初始化根web应用程序上下文。
     */
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }
    

4.RedisHttpSessionConfiguration类图

Spring-Session实现Session共享实现原理以及源码解析_第3张图片
RedisHttpSessionConfiguration类图
Spring-Session实现Session共享实现原理以及源码解析_第4张图片
RedisHttpSessionConfiguration注释

RedisHttpSessionConfiguration继承了SpringHttpSessionConfiguration

public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
        implements EmbeddedValueResolverAware, ImportAware {
        

4.1 SpringHttpSessionConfiguration 创建一个名称为springSessionRepositoryFilter的bean

@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;
    }


4.2 创建RedisHttpSessionConfiguration#RedisTemplate bean的名称为sessionRedisTemplate

@Bean
    public RedisTemplate sessionRedisTemplate(
            RedisConnectionFactory connectionFactory) {
            //实例化 RedisTemplate 
        RedisTemplate template = new RedisTemplate();
        //设置key序列化 StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        //设置Hash key  StringRedisSerializer
        template.setHashKeySerializer(new StringRedisSerializer());
        if (this.defaultRedisSerializer != null) {
            template.setDefaultSerializer(this.defaultRedisSerializer);
        }
        //设置 connectionFactory。第五步创建的(实际connectionFactory加载过程和讲解过程顺序不一样)
        template.setConnectionFactory(connectionFactory);
        return template;
    }

4.3 创建RedisHttpSessionConfiguration#RedisOperationsSessionRepository bean的名称为sessionRepository

    @Bean
    public RedisOperationsSessionRepository sessionRepository(
    //使用sessionRedisTemplate bean
            @Qualifier("sessionRedisTemplate") RedisOperations sessionRedisTemplate,
            ApplicationEventPublisher applicationEventPublisher) {
            
            //實例化RedisOperationsSessionRepository
        RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(
                sessionRedisTemplate);
                //設置applicationEventPublisher
        sessionRepository.setApplicationEventPublisher(applicationEventPublisher);
        //設置最大的失效時間 maxInactiveIntervalInSeconds = 1800
        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;
    }
    
  1. 创建 RedisConnectionFactory bean为 jedisConnectionFactory


    Spring-Session实现Session共享实现原理以及源码解析_第5张图片
    JedisConnectionFactory类图


2.SessionRepositoryFilter添加到FilterChain

流程:

Spring-Session实现Session共享实现原理以及源码解析_第6张图片
SessionRepositoryFilter添加到FIlterChain

说明:

1 2、在Servlet3.0规范中,Servlet容器启动时会自动扫描javax.servlet.ServletContainerInitializer的实现类,在实现类中我们可以定制需要加载的类。 通过注解@HandlesTypes(WebApplicationInitializer.class),让Servlet容器在启动该类时,会自动寻找所有的WebApplicationInitializer实现类。

2.1、insertSessionRepositoryFilter 方法通过filterName获取 SessionRepositoryFilter ,并创建了 new DelegatingFilterProxy(filterName);

3 4、然后将filter添加到FilterChain中


1.ServletContainerInitializer的实现类加载和通过注解@HandlesTypes(WebApplicationInitializer.class)实现类的加载

//加载实现类
@HandlesTypes(WebApplicationInitializer.class)
//SpringServletContainerInitializer实现ServletContainerInitializer
public class SpringServletContainerInitializer implements ServletContainerInitializer {

//------------


2.AbstractHttpSessionApplicationInitializer实现WebApplicationInitializer进行加载


@Order(100)
public abstract class AbstractHttpSessionApplicationInitializer
        implements WebApplicationInitializer {

2.1 onStartup

public void onStartup(ServletContext servletContext) throws ServletException {
        beforeSessionRepositoryFilter(servletContext);
        if (this.configurationClasses != null) {
            AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
            rootAppContext.register(this.configurationClasses);
            servletContext.addListener(new ContextLoaderListener(rootAppContext));
        }
        //添加Filter
        insertSessionRepositoryFilter(servletContext);
        afterSessionRepositoryFilter(servletContext);
    }

2.1.1.insertSessionRepositoryFilter


    /**
     * 注册springSessionRepositoryFilter
     * @param servletContext the {@link ServletContext}
     */
    private void insertSessionRepositoryFilter(ServletContext servletContext) {
// DEFAULT_FILTER_NAME = "springSessionRepositoryFilter"
        String filterName = DEFAULT_FILTER_NAME;
//通过filterName创建 DelegatingFilterProxy
        DelegatingFilterProxy springSessionRepositoryFilter = new DelegatingFilterProxy(
                filterName);
        String contextAttribute = getWebApplicationContextAttribute();
        if (contextAttribute != null) {
            springSessionRepositoryFilter.setContextAttribute(contextAttribute);
        }
//根据filterName和上下文添加Filter到FilterChain
        registerFilter(servletContext, true, filterName, springSessionRepositoryFilter);
    }

  1. registerFilter
    private void registerFilter(ServletContext servletContext,
            boolean insertBeforeOtherFilters, String filterName, Filter filter) {
        Dynamic registration = servletContext.addFilter(filterName, filter);
        if (registration == null) {
            throw new IllegalStateException(
                    "Duplicate Filter registration for '" + filterName
                            + "'. Check to ensure the Filter is only configured once.");
        }
        //是否支持异步,默认 true
        registration.setAsyncSupported(isAsyncSessionSupported());
        //得到DispatcherType springSessionRepositoryFilter
        EnumSet dispatcherTypes = getSessionDispatcherTypes();
        //添加一个带有给定url模式的筛选器映射和由这个FilterRegistration表示的过滤器的分派器类型。 过滤器映射按照添加它们的顺序进行匹配。
        registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters,
                "/*");
    }
  1. addFilter将Filter添加到ServletContext中
    public FilterRegistration.Dynamic addFilter(
        String filterName, Filter filter);

3.SessionRepositoryFilter拦截过程

流程:

Spring-Session实现Session共享实现原理以及源码解析_第7张图片
SessionRepositoryFilter拦截过程

说明:

1、请求被DelegatingFilterProxy : 拦截到,然后执行doFilter方法,在doFilter中找到执行的代理类。
2、OncePerRequestFilter : 代理Filter执行doFilter方法,然后调用抽象方法doFilterInternal
3、SessionRepositoryFilter 继承了OncePerRequestFilter,实现了doFilterInternal,这个方法一个封装一个wrappedRequest,通过执行commitSession保存session信息到redis

1请求进来,被DelegatingFilterProxy 拦截到,在web.xml中进行了配置
1.1 执行doFilter

如果没有指定目标bean名称,请使用筛选器名称。
@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        // 如果需要,延迟初始化委托。 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;
            }
        }

        // 让委托执行实际的doFilter操作
        invokeDelegate(delegateToUse, request, response, filterChain);
    }

1.2 initDelegate

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
//可以获取到SessionRepositoryFilter [备注1]
        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }

//[备注1] 因为 :SessionRepositoryFilter是一个优先级最高的javax.servlet.Filter
/*
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter
        extends OncePerRequestFilter {

*/
  1. delegate.doFilter();
    protected void invokeDelegate(
            Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        //代理去执行doFilter,代理为SessionRepositoryFilter
        delegate.doFilter(request, response, filterChain);
    }


2.1 OncePerRequestFilter#doFilter

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

            //在不调用此过滤器的情况下进行…
            filterChain.doFilter(request, response);
        }
        else {
            // 调用这个过滤器…
            request.setAttribute(this.alreadyFilteredAttributeName, Boolean.TRUE);
            try {
            //doFilterInternal是个抽象方法
                doFilterInternal(httpRequest, httpResponse, filterChain);
            }
            finally {
                // 删除此请求的“已过滤”请求属性。
                request.removeAttribute(this.alreadyFilteredAttributeName);
            }
        }
    }

  1. 执行SessionRepositoryFilter#doFilterInternal
@Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
        //使用HttpServletRequest 、HttpServletResponse和servletContext创建一个SessionRepositoryRequestWrapper

        SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
                request, response, this.servletContext);
        SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
                wrappedRequest, response);

//使用CookieHttpSessionStrategy重新包装了 HttpServletRequest
        HttpServletRequest strategyRequest = this.httpSessionStrategy
                .wrapRequest(wrappedRequest, wrappedResponse);
        HttpServletResponse strategyResponse = this.httpSessionStrategy
                .wrapResponse(wrappedRequest, wrappedResponse);

        try {
        //执行其他过滤器
            filterChain.doFilter(strategyRequest, strategyResponse);
        }
        finally {
        //保存session信息
            wrappedRequest.commitSession();
        }
    }

4 .wrappedRequest.commitSession() 看下第四大点分析

4.SessionRepository保存session数据

流程:


Spring-Session实现Session共享实现原理以及源码解析_第8张图片
SessionRepository保存session数据

说明:

1、提交session保存
2、获取当前session,这一步比较重要,获取了一个HttpSessionWrapper,这个HttpSessionWrapper替换了HTTPSession
3、wrappedSession获取当前的Session
4、使用 RedisTemplate 保存Session内容,并通过调用RedisConnection 使用它的实现类JedisClusterConnection获取redis连接

1.commitSession

/**
*使用HttpSessionStrategy将会话id写入响应。 *保存会话。
*/
private void commitSession() {
            HttpSessionWrapper wrappedSession = getCurrentSession();
            if (wrappedSession == null) {
                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())) {
                    SessionRepositoryFilter.this.httpSessionStrategy.onNewSession(session,
                            this, this.response);
                }
            }
        }

2.getCurrentSession

会话存储库请求属性名。
public static final String SESSION_REPOSITORY_ATTR = SessionRepository.class
            .getName();
            
private static final String CURRENT_SESSION_ATTR = SESSION_REPOSITORY_ATTR
            + ".CURRENT_SESSION";

private HttpSessionWrapper getCurrentSession() {
            return (HttpSessionWrapper)
            //获取session
            getAttribute(CURRENT_SESSION_ATTR);
        }
        
   /**
     * 此方法的默认行为是在包装请求对象上调用getAttribute(字符串名称)。
     */
    public Object getAttribute(String name) {
    //这里的request就是上面封装的
        return this.request.getAttribute(name);
    }

3 .wrappedSession.getSession

//返回 RedisSession
S session = wrappedSession.getSession();
//-------------------------
public S getSession() {
        return this.session;
    }
class ExpiringSessionHttpSession implements HttpSession {
    private S session;


final class RedisSession implements ExpiringSession {

4.save,实际是调用 RedisOperationsSessionRepository的 RedisOperations 操作

SessionRepositoryFilter.this.sessionRepository.save(session);

//this.sessionRepository =  SessionRepository sessionRepository;

//--------------------------------
//这个RedisOperationsSessionRepository是之前就创建好的
public class RedisOperationsSessionRepository implements
        FindByIndexNameSessionRepository,
        MessageListener {
        
        
public interface FindByIndexNameSessionRepository
        extends SessionRepository {
        
//---------------------------


    public void save(RedisSession session) {
        //4.1saveDelta
        session.saveDelta();
        if (session.isNew()) {
            //4.2调用
            String sessionCreatedKey = getSessionCreatedChannel(session.getId());
            //4.3convertAndSend
            //RedisOperations = this.sessionRedisOperations
            this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
            session.setNew(false);
        }
    }
    
    

其中RedisOperationsSessionRepository 里面介绍保存的详细过程,具体请看文档说明:

Class RedisOperationsSessionRepository

因为 RedisTemplate implements RedisOperations,实际进行操作的是RedisTemplate,RedisTemplate通过RedisConnection进行数据add和remove等

public class RedisTemplate
extends RedisAccessor
implements RedisOperations, BeanClassLoaderAware

总结

本系列到这里也就结束了,本次话的整个流程图,会上传到github上,使用Jude打开就可以看!

如果有什么地方写的不对或者有想和我一起探讨一下的,欢迎加我的QQ或者QQ群!

记录一个小点:

Spring Session + Redis实现分布式Session共享 有个非常大的缺陷, 无法实现跨域名共享session , 只能在单台服务器上共享session , 因为是依赖cookie做的 , cookie 无法跨域 pring Session一般是用于多台服务器负载均衡时共享Session的,都是同一个域名,不会跨域。你想要的跨域的登录,可能需要SSO单点登录。

参考博文

【Spring】Spring Session的简单搭建与源码阅读

利用spring session解决共享Session问题

Spring Session解决分布式Session问题的实现原理

spring-session简介、使用及实现原理


本系列教程

【入门】分布式Session一致性入门简介

【第一篇】Spring-Session实现Session共享入门教程

【第二篇】Spring-Session实现Session共享Redis集群方式配置教程

【第三篇】Spring-Session实现Session共享实现原理以及源码解析

本系列的源码下载地址:learn-spring-session-core


如果您觉得这篇博文对你有帮助,请点下面的喜欢,让更多的人看到,谢谢!

如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!


欢迎访问我的csdn博客,我们一同成长!

不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

博客首页http://blog.csdn.net/u010648555

你可能感兴趣的:(Spring-Session实现Session共享实现原理以及源码解析)