【Spring】Spring Session的簡單搭建與源碼閱讀


搭建一個簡單的Spring Session例子

引入依賴包

    <dependencies>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.7.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.8.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session</artifactId>
            <version>1.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
        </dependency>
        <dependency>
            <groupId>biz.paluch.redis</groupId>
            <artifactId>lettuce</artifactId>
            <version>3.5.0.Final</version>
        </dependency>
    </dependencies>

注冊Spring IoC、Spring Session和一些Servlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <display-name>Spring-Session-Redis</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
                classpath*:spring-session.xml
            </param-value>
    </context-param>

    <filter>
        <filter-name>springSessionRepositoryFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSessionRepositoryFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

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

    <servlet>
        <description></description>
        <display-name>SessionTestServlet</display-name>
        <servlet-name>SessionTestServlet</servlet-name>
        <servlet-class>com.nicchagil.SessionTestServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>SessionTestServlet</servlet-name>
        <url-pattern>/SessionTestServlet</url-pattern>
    </servlet-mapping>
    
</web-app>

最簡單的Spring Session的Bean配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans    
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd    
    http://www.springframework.org/schema/context   
    http://www.springframework.org/schema/context/spring-context-3.0.xsd 
    http://www.springframework.org/schema/mvc   
    http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
    
    <context:annotation-config />

    <!-- Jedis連接工廠 -->
    <bean id="jedisConnectionFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="nick-huang.example" />
        <property name="port" value="6379" />
    </bean>

    <bean id="redisHttpSessionConfiguration"
        class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <property name="maxInactiveIntervalInSeconds" value="1800" />
    </bean>

</beans>

一個測試的Servlet

package com.nicchagil;

import java.io.IOException;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class SessionTestServlet
 */
public class SessionTestServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private Logger logger = Logger.getLogger("SessionTestServlet");

    /**
     * Default constructor. 
     */
    public SessionTestServlet() {
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        
        String username = request.getParameter("username");
        if (username != null && username.length() > 0) {
            session.setAttribute("username", username);
        }
        
        response.getWriter().append("Served at: ").append(request.getContextPath()).append(", userName : " + session.getAttribute("username"));
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }

}

測試

啟動,用瀏覽器訪問該Servlet:http://127.0.0.1:8080/SessionTestServlet?username=123,然后不帶參數可能獲取用戶名:http://127.0.0.1:8080/SessionTestServlet。

看下Redis,有沒有持久化Session:

[root@blog ~]# /opt/redis-3.2.1/src/redis-cli 
127.0.0.1:6379> keys *
1) "spring:session:sessions:expires:5b29c067-a4b1-4d51-98b2-be084703fc78"
2) "spring:session:sessions:5b29c067-a4b1-4d51-98b2-be084703fc78"
3) "spring:session:expirations:1497195000000"

接下來看下實現原理。

委托過濾器代理類,DelegatingFilterProxy

這個類不在Spring Session中,但它是我們上述例子配置的入口。
它是委托過濾器代理類,可以看到它的繼承與實現關系:DelegatingFilterProxy -> GenericFilterBean -> Filter,其中GenericFilterBean的init()調用DelegatingFilterProxy的initFilterBean()。

initFilterBean()的作用是獲取委托的過濾器,並調用委托過濾器的doFilter(),這個過濾器是springSessionRepositoryFilter

Spring Session主要配置類,RedisHttpSessionConfiguration

說到springSessionRepositoryFilter,那么它在哪里實例化的呢?我們先看看RedisHttpSessionConfiguration
RedisHttpSessionConfiguration,這是一個配置類,它注冊了一些Spring Session所需的Bean。我們通過以下的xml顯式注冊一個配置Bean:

    <bean id="redisHttpSessionConfiguration"
        class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <property name="maxInactiveIntervalInSeconds" value="1800" />
    </bean>

這個類的繼承關系是:RedisHttpSessionConfiguration -> SpringHttpSessionConfigurationSpringHttpSessionConfiguration有個方法叫springSessionRepositoryFilter,這里就是注冊springSessionRepositoryFilterBean的地方。關於@Configuration、@Bean方式注冊Bean,請點擊這里

Session存儲過濾器,SessionRepositoryFilter

它的繼承關系是SessionRepositoryFilter -> OncePerRequestFilter -> Filter
OncePerRequestFilter的名字說明了其作用,看doFilter方法可知,它通過request中的一個Attribute判斷是否已做過濾,以保證對於每次請求只做一次此過濾邏輯。如果此請求是首次進入此過濾,則調用SessionRepositoryFilter.doFilterInternal

SessionRepositoryFilter.doFilterInternal,將Servlet容器傳入的Request和Response包裝成自己封裝的Request和Response,然后傳給下一任Filter,后續的Filter和Servlet都使用Spring Session封裝的Request和Response,此Request和Response分別繼承HttpServletRequestWrapper、HttpServletResponseWrapper,並作了自己業務的覆蓋。

HTTP請求包裝類,SessionRepositoryRequestWrapper

SessionRepositoryRequestWrapper根據自身業務,覆蓋了許多方法,這里不多討論,簡單舉例,比如getSession(boolean)

commitSession()

Http請求與Session的關系策略,HttpSessionStrategy

HttpSessionStrategy接口定義了幾個方法,另外有幾個實現類,這里只討論一部分:

// 從request獲取請求的SessionID,比如SessionID有可能放在Cookie或請求頭中
String getRequestedSessionId(HttpServletRequest request);

// 當新Session被創建且應通知客戶端新SessionID時此方法會被調用。此方法的實現可能為Cookie或響應頭設置新SessionID,當然也可以設置其他信息
void onNewSession(Session session, HttpServletRequest request,
        HttpServletResponse response);

// 當Session銷毀時且需通知客戶端該SessionID不再有效時會調用此方法。此方法的實現可能為從Cookie或響應頭移除SessionID。
void onInvalidateSession(HttpServletRequest request, HttpServletResponse response);

Cookie方式,CookieHttpSessionStrategy

getRequestedSessionId:

onNewSession:

onInvalidateSession:

HTTP請求頭方式,HeaderHttpSessionStrategy

HeaderHttpSessionStrategy相對簡單,根據頭鍵值x-auth-token,從請求中讀取sessionID,或設置響應頭。
值得注意的是,銷毀Session時onInvalidateSession設置響應頭x-auth-token的值為空。

Session持久化,SessionRepository

這是持久化Session的接口,定義有幾個方法:

// 創建能被此實現持久化的新Session。
S createSession();

// 持久化Session
void save(S session);

// 根據ID查詢Session
S getSession(String id);

// 刪除Session
void delete(String id);


免責聲明!

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



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