1. 程式人生 > >使用Spring-Session Redis實現Session共享

使用Spring-Session Redis實現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>
  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">

  
  • 1
  • 2

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等