【第三篇】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共享实现原理以及源码解析「建议收藏」

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

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • datagrip激活码 2021【在线注册码/序列号/破解码】

    datagrip激活码 2021【在线注册码/序列号/破解码】,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • word打印A4纸翻页小册子设置「建议收藏」

    word打印A4纸翻页小册子设置「建议收藏」要实现的是A4纸对折成翻页的小册子在word里选页面布局设置如下:还有页脚的页数显示要改成左页的页数在左下角,右页的页数在右下角:有页码显示时双击页脚会出来页码设置,选择双面打印2。转成pdf:点打印图标,选择导出pdf…

  • 贴片机保养步骤 保养项目 保养的目的与作用

    贴片机是一种高强度、长时间、超负荷的运转设备,需要定期进行细心的呵护保养,贴片机保养的好,运转稳定,保证日常生产产能,贴片机如衣食父母;贴片机保养不到位或经常不保养,性能再好的机子也会不堪重负出现各类毛病,影响生产产能和产品质量,因此贴片机保养是贴片厂非常重要的一环工作内容事项。(延伸阅读:何为SMT?smt是做什么的,smt贴片是什么意思?)贴片机日保用干净白布清洁机器表面灰尘,包含机身、显示器、键盘、鼠标、开关等;检测气压正常值;检查机器内部各装置是否正常;用吸尘器清洁机器里

  • 如何将sql文件导入mysql

    第一步:打在开始界面中找到mysql第二步:双击打开mysql软件。,并输入密码。第三步:如果sql文件的内容中有创建数据库的语句或者你想将表存放在你已有的数据库,在这里就不用创建数据库。第四步:输入“showdatabases;”就能看到自己创建的数据库。第五步:输入“use数据库名”,开始使用这个数据库。第六步:开始导入sql文件,输入“sourcesql文件的路径”(注意你的文件路径要…

  • 运维必须掌握的27道Linux面试题

    运维必须掌握的27道Linux面试题1.解释下什么是GPL,GNU,自由软件?GPL:(通用公共许可证):一种授权,任何人有权取得、修改、重新发布自由软件的权力。GNU:(革奴计划):目标是创建一套完全自由、开放的的操作系统。自由软件:是一种可以不受限制地自由使用、复制、研究、修改和分发的软件。主要许可证有GPL和BSD许可证两种。 2.如何选择Linux操作系统版本?一般来讲,桌面用

  • pfx证书生成「建议收藏」

    pfx证书生成「建议收藏」第一步:打开linux创建一个文件夹第二步:opensslreq-newkeyrsa:2048-nodes-keyout0.key-x509-days365-out0.cer根据提示输入国家代码,省,城市,组织,单位,名称,邮箱第三步:opensslpkcs12-export-in0.cer-inkey0.key-outyoufilename.pfx根据提示输入密码两次,*使用的时候会提示输入密码…

发表回复

您的电子邮箱地址不会被公开。

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