Spring-Session實現Session共享實現原理以及源碼解析


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

本篇介紹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

45、配置文件配置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>
    
    /**
	 * 初始化根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簡介、使用及實現原理


本系列教程

【第一篇】Spring-Session實現Session共享入門教程

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

【第三篇】Spring-Session實現Session共享實現原理以及源碼解析

本系列的源碼下載地址:learn-spring-session-core

備注: 由於本人能力有限,文中若有錯誤之處,歡迎指正。


謝謝你的閱讀,如果您覺得這篇博文對你有幫助,請點贊或者喜歡,讓更多的人看到!祝你每天開心愉快!


Java編程技術樂園:一個分享編程知識的公眾號。跟着老司機一起學習干貨技術知識,每天進步一點點,讓小的積累,帶來大的改變!

掃描關注,后台回復【秘籍】,獲取珍藏干貨! 99.9%的伙伴都很喜歡

image.png | center| 747x519

© 每天都在變得更好的阿飛雲


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM