知其然,还要知其所以然 !
本篇介绍Spring-Session的整个实现的原理。以及对核心的源码进行简单的介绍!
实现原理这里简单说明描述:
就是当Web服务器接收到http请求后,当请求进入对应的Filter进行过滤,将原本需要由web服务器创建会话的过程转交给Spring-Session进行创建,本来创建的会话保存在Web服务器内存中,通过Spring-Session创建的会话信息可以保存第三方的服务中,如:redis,mysql等。Web服务器之间通过连接第三方服务来共享数据,实现Session共享!
本次源码介绍基于上一篇内容,并且在保存Session的时候只会分析使用JedisConnectionFactory实现的RedisConnectionFactory !
流程:
说明:
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
代码分析如下:
<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>
3.
/** * 初始化根web应用程序上下文。 */ @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
4.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; }
创建 RedisConnectionFactory bean为 jedisConnectionFactory
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
流程:
说明:
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); }
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, "/*"); }
public FilterRegistration.Dynamic addFilter( String filterName, Filter filter);
流程:
说明:
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 { */
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); } } }
@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() 看下第四大点分析
流程:
说明:
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简介、使用及实现原理
【第一篇】Spring-Session实现Session共享入门教程
【第二篇】Spring-Session实现Session共享Redis集群方式配置教程
【第三篇】Spring-Session实现Session共享实现原理以及源码解析
本系列的源码下载地址:learn-spring-session-core
如果您觉得这篇博文对你有帮助,请点个赞,让更多的人看到,谢谢!
如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!
欢迎访问我的csdn博客,我们一同成长!
“ 不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢! ”
博客首页: http://blog.csdn.net/u010648555