【第三篇】Spring-Session实现Session共享实现原理以及源码解析「建议收藏」

知其然,还要知其所以然 !本篇介绍Spring-Session的整个实现的原理。以及对核心的源码进行简单的介绍!实现原理介绍实现原理这里简单说明描述: 就是当Web服务器接收到http请求后,当请求进入对应的Filter进行过滤,将原本需要由web服务器创建会话的过程转交给Spring-Session进行创建,本来创建的会话保存在Web服务器内存中,通过Spring-…

大家好,又见面了,我是全栈君。

做一个积极的人

编码、改bug、提升自己

我有一个乐园,面向编程,春暖花开!

=================================================

对人工智能感兴趣的伙伴,分享一个我朋友的人工智能教程。零基础!通俗易懂!风趣幽默!大家可以看看是否对自己有帮助,点击这里查看教程

=================================================

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

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

实现原理介绍

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

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

实现原理结构草图

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

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

1.SessionRepositoryFilter和JedisConnectionFactory注册过程

流程:

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

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

代码分析如下:

  1. web.xml ,加载了xml配置文件,并初始化web应用上下文
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:spring/*xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
  

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

<!--创建一个Spring Bean的名称springSessionRepositoryFilter实现过滤器。 筛选器负责将HttpSession实现替换为Spring会话支持。在这个实例中,Spring会话得到了Redis的支持。-->
    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>

    <!--创建了一个RedisConnectionFactory,它将Spring会话连接到Redis服务器。我们配置连接到默认端口(6379)上的本地主机!-->
    <!--集群Redis-->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <!--Redis-CLuster-->
        <constructor-arg index="0" ref="redisClusterConfig"/>

        <!--配置Redis连接池 ,可以不配置,使用默认就行!-->
        <constructor-arg index="1" ref="jedisPoolConfig"/>
    </bean>
    
  1. ContextLoaderListener
/** * 初始化根web应用程序上下文。 */
@Override
public void contextInitialized(ServletContextEvent event) { 
   
initWebApplicationContext(event.getServletContext());
}
	

4.RedisHttpSessionConfiguration类图

RedisHttpSessionConfiguration类图

RedisHttpSessionConfiguration注释
RedisHttpSessionConfiguration继承了SpringHttpSessionConfiguration

public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
		implements EmbeddedValueResolverAware, ImportAware { 
   
		

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

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


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

@Bean
public RedisTemplate<Object, Object> sessionRedisTemplate(
    RedisConnectionFactory connectionFactory) { 
   
    //实例化 RedisTemplate 
    RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
    //设置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<Object, Object> 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

JedisConnectionFactory类图

<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">

2.SessionRepositoryFilter添加到FilterChain

流程:

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<DispatcherType> dispatcherTypes = getSessionDispatcherTypes();
    //添加一个带有给定url模式的筛选器映射和由这个FilterRegistration表示的过滤器的分派器类型。 过滤器映射按照添加它们的顺序进行匹配。
    registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters,
                                          "/*");
}
  1. addFilter将Filter添加到ServletContext中
public FilterRegistration.Dynamic addFilter(
    String filterName, Filter filter);

3.SessionRepositoryFilter拦截过程

流程:

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<S extends ExpiringSession> 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数据

流程:
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<S extends ExpiringSession> implements HttpSession { 
   
    private S session;


 final class RedisSession implements ExpiringSession { 
   

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

SessionRepositoryFilter.this.sessionRepository.save(session);

//this.sessionRepository = SessionRepository<S> sessionRepository;

//--------------------------------
//这个RedisOperationsSessionRepository是之前就创建好的
public class RedisOperationsSessionRepository implements
    FindByIndexNameSessionRepository<RedisOperationsSessionRepository.RedisSession>,
MessageListener { 
   


    public interface FindByIndexNameSessionRepository<S extends Session>
        extends SessionRepository<S> { 
   

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


        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<K, V>
extends RedisAccessor
implements RedisOperations<K, V>, 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


谢谢你的阅读,如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到!祝你每天开心愉快!


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

愿你我在人生的路上能都变成最好的自己,能够成为一个独挡一面的人
【第三篇】Spring-Session实现Session共享实现原理以及源码解析「建议收藏」

© 每天都在变得更好的阿飞云

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/121108.html原文链接:https://javaforall.net

(0)
上一篇 2022年2月27日 上午10:54
下一篇 2022年2月27日 上午10:54


相关推荐

  • 「DeepSeek-V3-0324」接入CLine教程,完美替代并超越Cursor+Claude-3.7-Sonnet组合

    「DeepSeek-V3-0324」接入CLine教程,完美替代并超越Cursor+Claude-3.7-Sonnet组合

    2026年3月13日
    2
  • non-compatible bean definition_cannot create a session after

    non-compatible bean definition_cannot create a session after前些天在我使用OkHttp的时候开始运行时出现了这么两个错误。错误一StaticinterfacemethodsareonlysupportedstartingwithAndroidN(–min-api24)大概意思就是静态接口方法只从AndroidN开始使用。错误二Invoke-customsareonlysupportedstartin…

    2025年9月17日
    11
  • Java注解@NotNull

    Java注解@NotNull@Null被注释的元素必须为null@NotNull被注释的元素不能为null@AssertTrue被注释的元素必须为true@AssertFalse被注释的元素必须为false@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值@DecimalMin(val…

    2022年5月10日
    31
  • 狗网skinsdog CSGO开箱子网站支持直接取回,全新任务系统上线

    狗网skinsdog CSGO开箱子网站支持直接取回,全新任务系统上线skinsdog狗网CSGO饰品皮肤开箱网站可直接取回狗网skinsdogCSGO开箱子网站支持直接取回,全新任务系统上线官方链接:skinsdog.cc注册登录自动免费获得$0.8美金推广码:csgogo(注册使用送0.8美金)支付:微信支付宝状态:直接取回…

    2026年4月15日
    5
  • RDLC——画图表

    RDLC——画图表我们接着上一篇博文接下来我们来画一个柱形图我们就先默认选择第一个柱形图然后这里很关键 有人问 我这里的数据和我下面添加的姓名年龄数据不一样怎么办 一步一步来 我们先再添加一个 datatable 接着返回 report1 rdlc 修改一下表达式 course 也设定一下然后返回 form1 cs 添加的部分红色框起来了 privatevoidF Load objectsender Event

    2025年11月5日
    8
  • php tp框架,tp框架是什么意思

    php tp框架,tp框架是什么意思tp 框架是 ThinkPHP 框架的缩写 ThinkPHP 框架是为了简化企业级应用开发和敏捷 WEB 应用开发而诞生的 最早诞生于 2006 年初 2007 年元旦正式更名为 ThinkPHP 并且遵循 Apache2 开源协议发布 本文操作环境 windows10 DellG3 ThinkPHP6 tp 框架是 ThinkPHP 框架的缩写 ThinkPHP 是为了简化企业级应用开发和敏捷 WEB 应用开发而诞生的 最早诞生

    2026年3月19日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号