次世代的會話管理項目 Spring Session

歡迎你們前往騰訊雲+社區,獲取更多騰訊海量技術實踐乾貨哦~javascript

本文來自 雲+社區翻譯社,由 Tnecesoc編譯。

會話管理一直是 Java 企業級應用的重要部分。不過在很長的一段時間裏,這一部分都被咱們認爲是一個已解決的問題,而且也沒有什麼重大的創新出現。html

然而,微服務還有可橫向伸縮的雲原生應用這一現代趨勢揭露了現今的會話管理技術在設計上的一些缺陷,挑戰着咱們在過去 20 多年來對這一設計得出的一些結論。html5

本文會演示Spring Session API 爲了幫助咱們克服之前的會話管理方式的一些侷限所採起的方法。咱們將會先總結一下當前的會話管理技術的問題,而後深刻探討 Spring Session 解決這些問題所採起的策略。最後,咱們會總結 Spring Session 的工做方式以及在具體項目裏面的一些用法。java

Spring Session 爲企業級 Java 應用的會話管理領域帶來了革新,讓咱們能夠輕鬆作到:git

  • 編寫可橫向伸縮的雲原生應用
  • 將會話狀態的存儲外放到專門的外部會話存儲裏,好比 Redis 或 Apache Geode,後者以獨立於應用程序服務器的方式提供了高質量的存儲集羣
  • 在用戶經過 WebSocket 發出請求的時候保持 HttpSession 的在線狀態
  • 訪問來自非 Web 請求處理指令的會話數據,好比 JMS 消息處理指令
  • 爲每一個瀏覽器創建多個會話提供支持,從而構建更豐富的終端用戶體驗
  • 控制在客戶端和服務器間交換會話 ID 的方式,從而編寫在 HTTP 報文首部中提取會話 ID 而脫離對 Cookie 的依賴的 RESTul API

注意,Spring Session 項目其實並不依賴於 Spring 框架,所以咱們甚至能在不使用 Spring 框架的項目裏面用到它。github

傳統會話管理技術的問題

Spring Session 的目的是解決傳統的 JavaEE 會話管理技術的各類問題。下面就經過一些例子說明一些這方面的問題。web

構建可橫向伸縮的雲原生應用程序

從雲原生應用架構的視角來看,一個應用應該能夠經過在一個大型的虛擬機池裏運行更多的 Linux 容器來部署更多的實例的方式來獲得橫向的伸縮。好比,咱們能很輕鬆地將一個這樣的應用的 war 文件部署到 Cloud Foundry 或 Heroku 上的 Tomcat 裏面,而後在幾秒內擴展出 100 個應用程序實例,使得其中每一個實例都有 1GB 的 RAM。咱們還能夠將雲平臺設置成會根據用戶需求自動增減應用程序實例的數量。redis

不少應用都會把 HTTP 會話狀態存儲在運行應用代碼的 JVM 裏面。這很容易實現,並且存取的速度也很快。當一個應用實例加入或退出集羣的時候,HTTP 會話的存儲會在全部尚存的應用程序實例上從新進行平均的分配。在彈性雲環境中,咱們會運行數以百計的應用實例,且實例數量可能隨時發生快速的增減變化。這就帶來了一些問題:spring

  • HTTP 會話存儲的從新分配會成爲性能瓶頸;
  • 存儲大量會話所需的堆空間太大,會致使垃圾回收過程頻繁進行,並影響性能;
  • TCP 組播一般會被雲端的基礎架構所禁止,但會話管理器須要常常用它來發現加入或退出集羣的應用實例。

所以,將 HTTP 會話狀態存儲在運行應用代碼的 JVM 以外的數據存儲中會更高效。例如能夠設置並使用 Redis 來存儲上述的 100 個 Tomcat 實例裏面的會話狀態,那麼 Tomcat 實例數量的增減便不會影響到在 Redis 中的會話存儲的模式。另外,由於 Redis 是用 C 語言編寫的,因此它能夠在沒有垃圾回收機制影響其運行的前提下,動用數百 GB 甚至 TB 數量級的內存。apache

對像 Tomcat 這樣的開源服務器,找到使用外部數據存儲(如 Redis 或 Memcached)的會話管理技術的其餘實現是很簡單的,可是使用起來的配置過程可能很複雜,而且每一個應用服務器的配置過程可能都不同。對如 WebSphere 和 Weblogic 之類的閉源產品,找到適合它們的會話管理技術的替代實現則一般是不可能的。

Spring Session 爲設置插件式的會話數據存儲提供了一種獨立於具體應用服務器的方法,使得咱們能在 Servlet 框架的範疇內實現這樣的存儲,而不用依賴於具體的應用服務器的 API。這意味着 Spring Session 能夠與全部實現了 Servlet 規範的應用服務器(Tomcat,Jetty,WebSphere,WebLogic,JBoss)協同工做,並在全部應用服務器上以徹底相同且很容易的方式來進行配置。

咱們還能夠根據咱們的需求選用最適合的外部會話數據存儲。這使得 Spring Session 也成了一個能幫助咱們將傳統的 JavaEE 應用遷移到雲端並做爲一個符合十二要素的應用的一個理想的遷移工具。

一個用戶,多個帳戶

假設你正在 example.com 上運行一個面向大衆的 Web 應用,其中一些人類用戶建立了多個賬號。例如,用戶 Jeff Lebowski 可能有兩個賬號 thedude@example.com 和 lebowski@example.com。跟其餘 Java Web 應用程序同樣,你可使用 HttpSession 來跟蹤各類會話狀態,好比當前登陸的用戶。所以,當用戶想從 dude@example.com 切換到 lebowski@example.com 時,就必須註銷當前帳號並從新登陸。

使用 Spring Session 來爲每一個用戶配置多個 HTTP 會話就很簡單了。這時 Jeff Lebowski 無需註銷和登陸就能夠在 thedude@example.com 和 lebowski@example.com 之間來回切換。

不一樣安全級別下的預覽

想象一下,你要構建一個具備複雜的自定義受權體系的 Web 應用,其中具備不一樣權限的用戶會具備不一樣的應用 UI 樣式。

好比說,假設應用有四個安全級別:公開(public)、保密(confidential)、機密(secret)以及絕密(top secret)。在用戶登陸到應用時,系統會識別這一用戶的安全級別,而後只對其顯示不高於其安全級別的數據。這樣,公開級別的用戶能夠看到公開級別的文檔;具備保密級別的用戶能看公開和保密級別的,以此類推。爲了讓用戶界面更加友好,咱們的應用也應該能讓用戶預覽應用的 UI 在較低的安全級別下的樣子。好比絕密級別用戶應該能在祕密模式下預覽應用的各項事物的外觀。

典型的 Web 應用會將當前用戶的身份及其安全級別或角色存儲在 HTTP 會話裏面。不過,因爲 Web 應用的每一個用戶只有一個會話,所以也只能經過註銷再登陸的方式來切換用戶的角色,或者實現一個用戶多個會話這一形式。

憑藉 Spring Session,咱們就能夠很輕鬆地給每一個登陸用戶建立多個相互獨立的會話,預覽功能的實現也會所以變得簡單。好比當前以絕密等級登陸的用戶想要預覽機密等級下的應用時,就能夠對其建立並使用一個新的安全級別爲機密的會話。

在使用 Web Sockets 時保持登陸狀態

再想象一個場景,在用戶經過 example.com 登陸到咱們的 Web 應用時,他們能使用經過 Websockets 工做的一個 HTML5 即時聊天客戶端進行對話。不過,根據 Servlet 規範,經過 Websockets 發出的請求不會更新會話的過時時間,所以在用戶進行聊天的時候,不管他們的聊天有多頻繁,會話也可能聊着聊着就沒了,而後 Websocket 鏈接也會所以關閉,聊天也就沒法繼續了。

又是憑藉 Spring Session,咱們能夠很輕鬆地確保 Websocket 請求還有常規的 HTTP 請求都能更新會話的過時時間。

訪問對非 Web 請求的會話數據

再想象一下,咱們的應用提供了兩種訪問方式,一個基於 HTTP 的 RESTful API,另外一個是基於 RabbitMQ 的 AMQP 消息。此時,執行處理 AMQP 消息的的線程是沒法訪問應用服務器的 HttpSession 的,對此咱們必須本身寫一個解決方案來訪問 HTTP 會話裏邊的數據。

仍是憑藉 Spring Session,只要咱們知道會話的 ID,就能夠從應用程序的任意線程訪問 Spring Session。Spring Session 比以往的 Servlet HTTP 會話管理器有着功能更加豐富的 API,使得咱們只須要知道會話 ID 就能定位咱們想要找的會話。好比,咱們能夠用傳入消息的用戶標識字段來直接找到對應的會話。

Spring Session 的工做方式

如今傳統應用服務器在 HTTP 會話管理方面的侷限性已經在不一樣情境中展現過了,咱們再來看看 Spring Session 是如何解決這些問題的。

Spring Session 架構

在實現一個會話管理器的時候,有兩個關鍵問題必須獲得解決:

  • 如何建立一個高效、可靠、高可用的會話數據存儲集羣?
  • 如何肯定可以哪一個會話的實例與哪一個傳入的請求(形式有 HTTP、WebSocket、AMQP 等)相關聯?

不過在本質上,有個更關鍵的問題是:如何跨越不一樣的請求協議來傳輸一個會話的 ID?

第一個問題對 Spring Session 來講已被各類高可用可伸縮的集羣存儲(Redis、Gemfire、Apache Geode 等)很好地解決了。所以 Spring Session 也應該定義一組標準接口來使得對底層數據存儲的訪問能夠用不一樣的數據存儲來實現。Spring Session 在定義 SessionExpiringSession 這些基本的關鍵接口以外,也針對了不一樣數據存儲的訪問定義了關鍵接口 SessionRepository

  • org.springframework.session.Session 是定義會話基本功能的接口,例如屬性的設置和刪除。這個接口並不依賴於具體的底層技術,所以能夠比 Servlet 裏面的 HttpSession 適用於更多的狀況;
  • org.springframework.session.ExpiringSession 則擴展了 Session 接口。它提供了一些屬性,讓咱們能夠設置具備時效性的會話,並查詢這個會話是否已通過期。RedisSession 即是這個接口的一個實現範例。
  • org.springframework.session.SessionRepository 定義了建立,保存,刪除和查找會話的方法。將 Session 保存到數據存儲的實際邏輯便寫在這一接口的具體實現中。例如 RedisOperationsSessionRepository 即是這個接口的一個實現,它使用 Redis 來實現了會話的建立、保存以及刪除。

至於將請求關聯到特定會話實例的問題,Spring Session 則假定這一關聯的過程取決於特定的協議,由於客戶端和服務器在請求 / 響應週期期間就須要對所傳輸的會話 ID 達成一致。好比,若是客戶端發來一個 HTTP 請求,那麼會話就能夠經過 Cookie 或者 HTTP 報文首部來和請求相關聯。若是發來一個 HTTPS 請求,則可用 SSL 的 Session ID 字段來說會話與請求相關聯。若發來的是 JMS 消息,那也能夠用消息首部來存儲請求和響應間的會話 ID。

對 HTTP 協議的關聯操做,Spring 會話定義了一個 HttpSessionStrategy 接口,後者有將 Cookies 和會話關聯在一塊兒的 CookieHttpSessionStrategy 和使用了自定義報文首部字段來管理會話的 HeaderHttpSessionStrategy 兩種實現。

下面便詳細地介紹一下 Spring Session 在 HTTP 協議上的工做方式。

在本文發佈時(2015.11.10),Spring Session 1.0.2 在當前的 GA 發行版提供了使用 Redis 的 Spring Session 的一套實現,以及支持任何分佈式的 Map(如 Hazelcast)的實現。其實,實現 Spring Session 針對某種數據存儲的支持是相對容易的,在開源社區裏已經有了不少這樣的實現。

基於 HTTP 的 Spring Session

基於 HTTP 的 Spring Session 是以一個標準 Servlet 過濾器(filter)的形式實現的。這一過濾器應該截取全部的對 Web 應用的請求,而且也應該在諸多過濾器組成的鏈中排在第一個。Spring Session 的過濾器會負責確保全部後續的代碼裏面對 javax.servlet.http.HttpServletRequest.getSession() 方法的調用都會呈遞給一個 Spinrg Session 的 HttpSession 實例,而不是應用服務器默認提供的 HttpSession

要理解這點,最簡單的方法就是查閱 Spring Session 的實際源碼。咱們首先從用來實現 Spring Session 的標準 Servlet 擴展點(extension points)開始。

在 2001 年,Servlet 2.3 規範引入了 ServletRequestWrapper。該類的 Javadoc 稱 ServletRequestWrapper 「爲 ServletRequest 接口能讓開發者繼承它來適配一種特別的 Servlet 提供了一種便利的實現。該類採用了包裝器,或者說裝飾器模式。對該類的 ServletRequest 類的方法的調用會被傳至其封裝的一個請求對象裏去。」 下面這段從 Tomcat 裏抽出來的代碼就展現了 ServletRequestWrapper 的實現方式。

public class ServletRequestWrapper implements ServletRequest {

    private ServletRequest request;

    /**
     * Creates a ServletRequest adaptor wrapping the given request object. 
     * 建立一個裝有給定的請求對象的 ServletRequest 適配器
     * @throws java.lang.IllegalArgumentException if the request is null
     * 若是請求對象爲空就會拋出空指針異常
     */
    public ServletRequestWrapper(ServletRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("Request cannot be null");   
        }
        this.request = request;
    }

    public ServletRequest getRequest() {
        return this.request;
    }
    
    public Object getAttribute(String name) {
        return this.request.getAttribute(name);
    }

    // 爲可讀性着想, 接下來的代碼就略了
}

Servlt 2.3 規範還對 ServletRequestWrapper 定義了一個子類 HttpServletRequestWrapper。咱們能夠用它來快速地實現一個自定義的 HttpServletRequest。下面這段從 Tomcat 裏抽出來的代碼就展現了 HttpServletRequestWrapper 這個類的實現方式。

public class HttpServletRequestWrapper extends ServletRequestWrapper 
    implements HttpServletRequest {

    public HttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }
    
    private HttpServletRequest _getHttpServletRequest() {
        return (HttpServletRequest) super.getRequest();
    }
  
    public HttpSession getSession(boolean create) {
     return this._getHttpServletRequest().getSession(create);
    }
   
    public HttpSession getSession() {
      return this._getHttpServletRequest().getSession();
    }
    
    // 爲可讀性着想,接下來的代碼就略了  
}

所以,咱們就能夠用這些包裝類來編寫一些擴展 HttpServletRequest 功能的代碼,重載返回 HttpSession 的方法,使得後者返回的是咱們存儲在外部存儲倉庫裏面的會話。這裏就給出一份從 Spring Session 項目提出來的源碼就對應了這裏提到的東西。爲了能對應這裏的解釋,源碼裏面本來的註釋被我重寫了一下,在此不妨也看一看裏面的註釋。

/*
 * Spring Session 項目定義了一個繼承了標準 HttpServletRequestWrapper 的類
 * 它重載了 HttpServletRequest 裏面的全部跟會話有關的方法
 */
private final class SessionRepositoryRequestWrapper
   extends HttpServletRequestWrapper {

   private HttpSessionWrapper currentSession;
   private Boolean requestedSessionIdValid;
   private boolean requestedSessionInvalidated;
   private final HttpServletResponse response;
   private final ServletContext servletContext;

   /*
   * 構造方法這塊很是簡單
   * 它會接收並設置一些以後會用到的參數,
   * 而後完成對 HttpServletRequestWrapper 的代理
   */
   private SessionRepositoryRequestWrapper(
      HttpServletRequest request,
      HttpServletResponse response,
      ServletContext servletContext) {
     super(request);
     this.response = response;
     this.servletContext = servletContext;
   }

   /*
   * Spring Session 便在這裏用本身對返回存儲於外部數據源的會話數據的實現
   * 取代了對應用服務器提供的默認方法的代理調用.
   * 
   * 這裏的實現會先檢查它是否是已經有一個對應的會話. 
   * 如有那就返回之, 不然就會檢查當前的請求附帶的會話 ID 是否確實對應着一個會話
   * 如有, 那就用這個會話 ID 從 SessionRepository 裏邊加載這個會話;
   * 若外部數據源裏沒這個會話, 或者這個會話 ID 沒對應的會話,
   * 那就建立一個新的會話, 並把它存在會話數據存儲裏面.
   */
   @Override
   public HttpSession getSession(boolean create) {
     if(currentSession != null) {
       return currentSession;
     }
     String requestedSessionId = getRequestedSessionId();
     if(requestedSessionId != null) {
       S session = sessionRepository.getSession(requestedSessionId);
       if(session != null) {
         this.requestedSessionIdValid = true;
         currentSession = new HttpSessionWrapper(session, getServletContext());
         currentSession.setNew(false);
         return currentSession;
       }
     }
     if(!create) {
       return null;
     }
     S session = sessionRepository.createSession();
     currentSession = new HttpSessionWrapper(session, getServletContext());
     return currentSession;
   }

   @Override
   public HttpSession getSession() {
     return getSession(true);
   }
}

Spring Session 同時定義了一個 ServletFilter 接口的實現類 SessionRepositoryFilter。這裏也會給出這個過濾器的實現的核心部分的源碼,而且也會附上一些對應本文內容的註釋,不妨也看一看。

/*
 * SessionRepositoryFilter 是一個標準 ServletFilter 的實現.
 * 其目的是從它的基類擴展出一些功能來.
 */
public class SessionRepositoryFilter < S extends ExpiringSession >
    extends OncePerRequestFilter {

    /*
     * 這一方法就是核心部分.
     * 該方法會建立一個咱們在上面介紹過的包裝請求的實例,
     * 而後拿這個包裝過的請求再過一遍過濾器鏈的剩餘部分.
     * 關鍵的地方在於,應用在執行位於這個過濾器以後的代碼時,
     * 若是要獲取會話的數據, 那這個包裝過的請求就會返回 Spring Session
     * 所保存在外部數據源的 HttpServletSession 實例.
     */
    protected void doFilterInternal(
        HttpServletRequest request,
        HttpServletResponse response,
        FilterChain filterChain) throws ServletException, IOException {

        request.setAttribute(SESSION_REPOSITORY_ATTR, sessionRepository);

        SessionRepositoryRequestWrapper wrappedRequest =
          new SessionRepositoryRequestWrapper(request,response,servletContext);

        SessionRepositoryResponseWrapper wrappedResponse =
          new SessionRepositoryResponseWrapper(wrappedRequest, response);

        HttpServletRequest strategyRequest =
             httpSessionStrategy.wrapRequest(wrappedRequest, wrappedResponse);

        HttpServletResponse strategyResponse =
             httpSessionStrategy.wrapResponse(wrappedRequest, wrappedResponse);

        try {
            filterChain.doFilter(strategyRequest, strategyResponse);
        } finally {
            wrappedRequest.commitSession();
        }
    }
}

這一節的重點在於,基於 HTTP 的 Spring Session 其實也只是一個用了 Servlet 規範的標準特性來實現功能的經典的 Servlet 過濾器而已。所以,將現有的 Web 應用的 war 文件改爲使用 Spring Session 是應該能夠不用改動已有代碼的。然而,在應用裏面用了 javax.servlet.http.HttpSessionListener 的狀況則是例外。Spring Session 1.0 並無對 HttpSessionListener 提供支持,不過 Spring Session 1.1 M1 版本則對其添加了支持。詳情見此

Spring Session 的設置

在 Web 項目裏面,Spring Session 的設置分爲四步:

  • 設置在 Spring Session 中使用的數據存儲
  • 將 Spring Session 的 .jar 文件添加到 Web 應用中
  • 將 Spring Session 的過濾器添加到 Web 應用的配置中
  • 設置從 Spring Session 到所選會話數據存儲的鏈接

Spring Session 內置了對 Redis 的支持。安裝和設置 redis 的詳細信息見此

完成上述 Spring Session 的設置步驟的常見方式有兩種。一種是使用 Spring Boot 來自動設置 Spring Session。另一種則是手動完成每個配置步驟。

用 Maven 和 Gradle 等依賴管理工具能夠很輕鬆地將 Spring Session 加入到應用的依賴項目裏面。好比說,若是你用的是 Spring Boot + Maven,那麼就能夠在 pom.xml 裏面加上如下依賴項目:

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session</artifactId>
    <version>1.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

spring-boot-starter-redis 這一依賴項目會確保跟 redis 交互所需的 jar 包都包含在應用裏面,這樣即可以使用 Spring Boot 來進行自動的配置。至於 spring-session 這一依賴項目則對應 Spring Session 的 jar 包。

設置 Spring Session Servlet 過濾器的過程能夠經過 Spring Boot 自動完成,只須要在 Spring Boot 的配置類裏面加上 @EnableRedisHttpSession 註解便可。就跟下面這段代碼同樣:

@SpringBootApplication
@EnableRedisHttpSession
public class ExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }
}

將下面這些配置信息加到 Spring Boot 的 application.properties 文件便可設置 Spring Session 到 Redis 的鏈接。

spring.redis.host=localhost
spring.redis.password=secret
spring.redis.port=6379

爲了設置和 Redis 的鏈接,Spring Boot 提供了一套詳實的底層架構,使得咱們能夠在其中任意設置一種跟 Redis 創建鏈接的方式。你能在 Spring Session 還有 Spring Boot 裏面找到循序漸進進行的指南

使用 web.xml 來設置傳統的 Web 應用去使用 Spring Session 的教程見此

設置傳統的不帶有 web.xml 的 war 文件去使用 Spring Session 的教程見此

在默認狀況下,Spring Session 會使用 HTTP cookie 來存儲會話 ID,可是咱們也能夠將 Spring Session 設置成使用自定義的 HTTP 報文首部字段(例如 x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3

)來存儲會話 ID,而這在構建 RESTful API 的時候會很是有用。完整教程見此

Spring Session 的用法

在配置了 Spring Session 以後,咱們就可使用標準的 Servlet API 去和它進行交互了。好比下面這段代碼就定義了一個使用標準 Servlet 會話 API 來訪問會話數據的 servlet。

@WebServlet("/example")
public class Example extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    // 使用標準的 servlet API 去獲取對應的會話數據
    // 這一會話數據就是 Spring Session 存在 Redis
    // 或是別的咱們所指定的數據源裏面的會話數據

    HttpSession session = request.getSession();
    String value = session.getAttribute(“someAttribute”);

  }
}

一個瀏覽器,多個會話

Spring Session 經過使用一個叫作 _s 的會話代號參數來跟蹤每一個用戶的多個會話。假若有個傳入請求的 URL 是 http://example.com/doSomething?_s=0,那麼 Spring Session 就會讀取 _s 參數的值,而後便會認爲這個請求對應的是默認的會話。

若是傳入請求的 URL 是 http://example.com/doSomething?_s=1,那麼 Spring Session 就會知道這個請求對應的會話的代號是 1。若是傳入請求沒有指定參數 _s,那麼 Spring Session 就會把它視爲對應默認對話(即 _s = 0)。

爲了讓每一個瀏覽器都建立一個新的會話,咱們只需像之前那樣調用 javax.servlet.http.HttpServletRequest.getSession(),而後 Spring Session 就會返回對應的會話,或者使用 Servlet 規範的語義建立一個新的會話。下表便給出了 getSession() 方法在同一瀏覽器的不一樣的 URL 參數下的具體表現形式:

HTTP 請求 URL 會話代號 getSession() 的具體表現
example.com/resource 0 若是存在與代號 0 相關聯的會話就返回之,不然就建立一個新會話,而後將其與代號 0 關聯起來
example.com/resource?_s=1 1 若是存在與代號 1 相關聯的會話就返回之,不然就建立一個新會話,而後將其與代號 1 關聯起來
example.com/resource?_s=0 0 若是存在與代號 0 相關聯的會話就返回之,不然就建立一個新會話,而後將其與代號 0 關聯起來
example.com/resource?_s=abc abc 若是存在與代號 abc 相關聯的會話就返回之,不然就建立一個新會話,而後將其與代號 abc 關聯起來

如上表所示,會話代號並不侷限於整數,只要與發佈給該用戶的全部其餘會話別名不一樣,便可對一個一個新的會話。然而,整數類型的會話代號應該是最易用的,而且 Spring Session 也給出了 HttpSessionManager 來提供一些處理會話代號的實用方法。

咱們能夠經過 "org.springframework.session.web.HttpSessionManager" 這個屬性名來查找相應屬性,進而訪問到 HttpSessionManager。下面這段代碼就演示了得到 HttpSessionManager 的引用的方法,以及這個實用方法類的一些主要的方法。

@WebServlet("/example")
public class Example extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest request,HttpServletResponse response)
  throws ServletException, IOException {

    /*
     * 經過使用 "org.springframework.session.web.http.HttpSessionManager"
     * 這一屬性名在請求屬性中查找屬性
     * 來獲取一個 Spring Session 的 HttpSessionManager 的引用
     */

    HttpSessionManager sessionManager=(HttpSessionManager)request.getAttribute(
        "org.springframework.session.web.http.HttpSessionManager");

    /*
     * 用 HttpSessionManager 來找出 HTTP 請求所對應的會話代號.
     * 默認狀況下這個會話代號會由 HTTP 請求的 URL 參數 _s 給出。
     * 好比 http://localhost:8080/example?_s=1 這個 URL
     * 就會讓這裏的 println() 方法打印 "Requested Session Alias is: 1"
     */
    String requestedSessionAlias=sessionManager.getCurrentSessionAlias(request);
    System.out.println("Requested Session Alias is:  " + requestedSessionAlias);

    /* 
     * 返回一個當前還沒被瀏覽器用在請求參數裏的惟一的會話代號.
     * 注意這一方法並不會建立一個新的會話, 
     * 建立新的會話仍是要經過 request.getSession() 來進行.
     */
    String newSessionAlias = sessionManager.getNewSessionAlias(request);

    /* 
     * 使用剛剛獲得的新會話代號構造一個 URL,
     * 使其含有 _s 這個參數.
     * 好比若 newSessionAlias 的值是 2,
     * 那麼這個方法就會返回 "/inbox?_s=3" 
     */
    String encodedURL = sessionManager.encodeURL("/inbox", newSessionAlias);
    System.out.println(encodedURL);

    /* 
     * 返回一個會話代號爲鍵, 會話 ID 爲值的 Map, 
     * 以便識別瀏覽器發來的請求所對應的會話.
     */
    Map <String, String> sessionIds = sessionManager.getSessionIds(request);
  }
}

結論

Spring Session 爲企業級 Java 應用的會話管理領域帶來了革新,讓咱們能夠輕鬆作到:

  • 編寫可橫向伸縮的雲原生應用
  • 將會話狀態的存儲外放到專門的外部會話存儲裏,好比 Redis 或 Apache Geode,後者以獨立於應用程序服務器的方式提供了高質量的存儲集羣
  • 在用戶經過 WebSocket 發出請求的時候保持 HttpSession 的在線狀態
  • 訪問來自非 Web 請求處理指令的會話數據,好比 JMS 消息處理指令
  • 爲每一個瀏覽器創建多個會話提供支持,從而構建更豐富的終端用戶體驗
  • 控制在客戶端和服務器間交換會話 ID 的方式,從而編寫在 HTTP 報文首部中提取會話 ID 而脫離對 Cookie 的依賴的 RESTul API

若你在尋找一種從傳統又笨重的應用服務器中解放的方法,但又囿於對應用服務器的會話存儲集羣功能的依賴,那麼 Spring Session 對像 Tomcat、Jetty 還有 Undertow 這樣的容器的輕量化來講是很好的一個選擇。

參考資料

Spring Session 項目

Spring Session 教程及指南

Websocket / HttpSession 超時交互

網絡研討會:Spring Session 導論

問答
傳統Web應用程序和API中的身份驗證、受權和會話管理如何實現?
相關閱讀
架構設計之Spring Session分佈式集羣會話管理 
Spring Session關鍵類源碼分析
一個能夠把web表單變成會話形式的開源框架

此文已由做者受權騰訊雲+社區發佈,原文連接:https://cloud.tencent.com/dev...

歡迎你們前往騰訊雲+社區或關注雲加社區微信公衆號(QcloudCommunity),第一時間獲取更多海量技術實踐乾貨哦~

相關文章
相關標籤/搜索