經過 Spring Session 實現新一代的 Session 管理

長期以來,session 管理就是企業級 Java 中的一部分,以至於咱們潛意識就認爲它是已經解決的問題,在最近的記憶中,咱們沒有看到這個領域有很大的革新。html

可是,現代的趨勢是微服務以及可水平擴展的原生雲應用(cloud native application),它們會挑戰過去 20 多年來咱們設計和構建 session 管理器時的前提假設,而且暴露了現代化 session 管理器的不足。html5

本文將會闡述最近發佈的 Spring Session API 如何幫助咱們克服眼下 session 管理方式中的一些不足,在企業級 Java 中,傳統上都會採用這種舊的方式。咱們首先會簡單闡述一下當前 session 管理中的問題,而後深刻介紹 Spring Session 是如何解決這些問題的。在文章的最後,將會詳細展現 Spring Session 是如何運行的,以及在項目中怎樣使用它。java

Spring Session 爲企業級 Java 應用的 session 管理帶來了革新,使得如下的功能更加容易實現:git

  • 編寫可水平擴展的原生雲應用。
  • 將 session 所保存的狀態卸載到特定的外部 session 存儲中,如 Redis 或 Apache Geode 中,它們可以以獨立於應用服務器的方式提供高質量的集羣。
  • 當用戶使用 WebSocket 發送請求的時候,可以保持 HttpSession 處於活躍狀態。
  • 在非 Web 請求的處理代碼中,可以訪問 session 數據,好比在 JMS 消息的處理代碼中。
  • 支持每一個瀏覽器上使用多個 session,從而可以很容易地構建更加豐富的終端用戶體驗。
  • 控制 session id 如何在客戶端和服務器之間進行交換,這樣的話就能很容易地編寫 Restful API,由於它能夠從 HTTP 頭信息中獲取 session id,而沒必要再依賴於 cookie。

須要說明的很重要的一點就是,Spring Session 的核心項目並不依賴於 Spring 框架,因此,咱們甚至可以將其應用於不使用 Spring 框架的項目中。github

傳統 session 管理的問題

傳統的 JavaEE session 管理會有各類問題,這剛好是 Spring Session 所要試圖解決的。這些問題在下面以樣例的形式進行了闡述。web

構建可水平擴展的原生雲應用

在原生的雲應用架構中,會假設應用可以進行擴展,這是經過在 Linux 容器中運行更多的應用程序實例實現的,這些容器會位於一個大型的虛擬機池中。例如,咱們能夠很容易地將一個「.war」文件部署到位於 Cloud Foundry 或 Heroku 的 Tomcat 中,而後在幾秒鐘的時間內就能擴展到 100 個應用實例,每一個實例能夠具備 1GB RAM。咱們還能夠配置雲平臺,基於用戶的需求自動增長和減小應用實例的數量。redis

在不少的應用服務器中,都會將 HTTP session 狀態保存在 JVM 中,這個 JVM 與運行應用程序代碼的 JVM 是同一個,由於這樣易於實現,而且速度很快。當新的應用服務器實例加入或離開集羣時,HTTP session 會基於現有的應用服務器實例進行從新平衡。在彈性的雲環境中,咱們會擁有上百個應用服務器實例,而且實例的數量可能在任意時刻增長或減小,這樣的話,咱們就會遇到一些問題:spring

  • 重平衡 HTTP session 可能會成爲性能瓶頸。
  • 爲了存儲大量的 session,會須要很大的堆空間,這會致使垃圾收集,從而對性能產生負面影響。
  • 雲基礎設施一般會禁止 TCP 多播(multicast),可是 session 管理器經常會使用這種機制來發現哪個應用服務器實例加入或離開了集羣。

所以,更爲高效的辦法是將 HTTP session 狀態保存在獨立的數據存儲中,這個存儲位於運行應用程序代碼的 JVM 以外。例如,咱們能夠將 100 個 Tomcat 實例配置爲使用 Redis 來存儲 session 狀態,當 Tomcat 實例增長或減小的時候,Redis 中所存儲的 session 並不會受到影響。同時,由於 Redis 是使用 C 語言編寫的,因此它可使用上百 GB 甚至 TB 級別的 RAM,它不會涉及到垃圾收集的問題。數據庫

對於像 Tomcat 這樣的開源服務器,很容易找到 session 管理器的替代方案,這些替代方案可使用外部的數據存儲,如 Redis 或 Memcached。可是,這些配置過程可能會比較複雜,並且每種應用服務器都有所差異。對於閉源的產品,如 WebSphere 和 Weblogic,尋找它們的 session 管理器替代方案不只很是困難,在有些時候,甚至是沒法實現的。後端

Spring Session 提供了一種獨立於應用服務器的方案,這種方案可以在 Servlet 規範以內配置可插拔的 session 數據存儲,不依賴於任何應用服務器的特定 API。這就意味着 Spring Session 可以用於實現了 servlet 規範的全部應用服務器之中(Tomcat、Jetty、 WebSphere、WebLogic、JBoss 等),它可以很是便利地在全部應用服務器中以徹底相同的方式進行配置。咱們還能夠選擇任意最適應需求的外部 session 數據存儲。這使得 Spring Session 成爲一個很理想的遷移工具,幫助咱們將傳統的 JavaEE 應用轉移到雲中,使其成爲知足  12-factor 的應用

每一個用戶有多個帳號

假設咱們在 example.com 上運行面向公衆的 Web 應用,在這個應用中有些用戶會建立多個帳號。例如,用戶 Jeff Lebowski 可能會有兩個帳戶 thedude@example.com 和 lebowski@example.com。和其餘 Java Web 應用同樣,咱們會使用HttpSession來跟蹤應用的狀態,如當前登陸的用戶。因此,當用戶但願從 thedude@example.com 切換到 lebowski@example.com 時,他必需要首先退出,而後再從新登陸回來。

藉助 Spring Session,爲每一個用戶配置多個 HTTP session 會很是容易,這樣用戶在 thedude@example.com 和 lebowski@example.com 之間切換的時候,就不須要退出和從新登陸了。

多級別的安全預覽

假設咱們正在構建的 Web 應用有一個複雜、自定義的權限功能,其中應用的 UI 會基於用戶所授予的角色和權限實現自適應。

例如,假設應用有四個安全級別:public、confidential、secret 和 top secret。當用戶登陸應用以後,系統會判斷用戶所具備的最高安全級別而且只會顯示該級別和該級別之下的數據。因此,具備 public 權限的用戶只能看到 public 級別的文檔,具備 secret 權限的用戶可以看到 public、confidential 和 secret 級別的文檔,諸如此類。爲了保證用戶界面更加友好,應用程序應該容許用戶預覽在較低的安全級別條件下頁面是什麼樣子的。例如,top secret 權限的用戶可以將應用從 top secret 模式切換到 secret 模式,這樣就能站在具備 secret 權限用戶的視角上,查看應用是什麼樣子的。

典型的 Web 應用會將當前用戶的標識及其角色保存在 HTTP session 中,但由於在 Web 應用中,每一個登陸的用戶只能有一個 session,所以除了用戶退出並從新登陸進來,咱們並無辦法在角色之間進行切換,除非咱們爲每一個用戶自行實現多個 session 的功能。

藉助 Spring Session,能夠很容易地爲每一個登陸用戶建立多個 session,這些 session 之間是徹底獨立的,所以實現上述的預覽功能是很是容易的。例如,當前用戶以 top secret 角色進行了登陸,那麼應用能夠建立一個新的 session,這個 session 的最高安全角色是 secret 而不是 top secret,這樣的話,用戶就能夠在 secret 模式預覽應用了。

當使用 Web Socket 的時候保持登陸狀態

假設用戶登陸了 example.com 上的 Web 應用,那麼他們可使用 HTML5 的 chat 客戶端實現聊天的功能,這個客戶端構建在 websocket 之上。按照 servlet 規範,經過 websocket 傳入的請求並不能保持 HTTP session 處於活躍狀態,因此當用戶在聊天的過程當中,HTTP session 的倒數計時器會在不斷地流逝。即使站在用戶的立場上,他們一直在使用應用程序,HTTP session 最終也可能會出現過時。當 HTTP session 過時時,websocket 鏈接將會關閉。

藉助 Spring Session,對於系統中的用戶,咱們可以很容易地實現 websocket 請求和常規的 HTTP 請求都能保持 HTTP session 處於活躍狀態。

非 Web 請求訪問 Session 數據

假設咱們的應用提供了兩種訪問方式:一種使用基於 HTTP 的 REST API,而另外一種使用基於 RabbitMQ 的 AMQP 消息。執行消息處理代碼的線程將沒法訪問應用服務器的 HttpSession,因此咱們必需要以一種自定義的方案來獲取 HTTP session 中的數據,這要經過自定義的機制來實現。

經過使用 Spring Session,只要咱們可以知道 session 的 id,就能夠在應用的任意線程中訪問 Spring Session。所以,Spring Session 具有比 Servlet HTTP session 管理器更爲豐富的 API,只要知道了 session id,咱們就能獲取任意特定的 session。例如,在一個傳入的消息中可能會包含用戶 id 的 header 信息,藉助它,咱們就能夠直接獲取 session 了。

Spring Session 是如何運行的

咱們已經討論了在傳統的應用服務器中,HTTP session 管理存在不足的各類場景,接下來看一下 Spring Session 是如何解決這些問題的。

Spring Session 的架構

當實現 session 管理器的時候,有兩個必需要解決的核心問題。首先,如何建立集羣環境下高可用的 session,要求可以可靠並高效地存儲數據。其次,無論請求是 HTTP、WebSocket、AMQP 仍是其餘的協議,對於傳入的請求該如何肯定該用哪一個 session 實例。實質上,關鍵問題在於:在發起請求的協議上,session id 該如何進行傳輸?

Spring Session 認爲第一個問題,也就是在高可用可擴展的集羣中存儲數據已經經過各類數據存儲方案獲得瞭解決,如 Redis、GemFire 以及 Apache Geode 等等,所以,Spring Session 定義了一組標準的接口,能夠經過實現這些接口間接訪問底層的數據存儲。Spring Session 定義了以下核心接口:Session、ExpiringSession以及SessionRepository,針對不一樣的數據存儲,它們須要分別實現。

  • org.springframework.session.Session接口定義了 session 的基本功能,如設置和移除屬性。這個接口並不關心底層技術,所以可以比 servlet HttpSession 適用於更爲普遍的場景中。
  • org.springframework.session.ExpiringSession擴展了 Session 接口,它提供了判斷 session 是否過時的屬性。RedisSession 是這個接口的一個樣例實現。
  • org.springframework.session.SessionRepository定義了建立、保存、刪除以及檢索 session 的方法。將 Session 實例真正保存到數據存儲的邏輯是在這個接口的實現中編碼完成的。例如,RedisOperationsSessionRepository 就是這個接口的一個實現,它會在 Redis 中建立、存儲和刪除 session。

Spring Session 認爲將請求與特定的 session 實例關聯起來的問題是與協議相關的,由於在請求 / 響應週期中,客戶端和服務器之間須要協商贊成一種傳遞 session id 的方式。例如,若是請求是經過 HTTP 傳遞進來的,那麼 session 能夠經過 HTTP cookie 或 HTTP Header 信息與請求進行關聯。若是使用 HTTPS 的話,那麼能夠藉助 SSL session id 實現請求與 session 的關聯。若是使用 JMS 的話,那麼 JMS 的 Header 信息可以用來存儲請求和響應之間的 session id。

對於 HTTP 協議來講,Spring Session 定義了HttpSessionStrategy接口以及兩個默認實現,即CookieHttpSessionStrategyHeaderHttpSessionStrategy,其中前者使用 HTTP cookie 將請求與 session id 關聯,然後者使用 HTTP header 將請求與 session 關聯。

以下的章節詳細闡述了 Spring Session 使用 HTTP 協議的細節。

在撰寫本文的時候,在當前的 Spring Session 1.0.2 GA 發佈版本中,包含了 Spring Session 使用 Redis 的實現,以及基於 Map 的實現,這個實現支持任意的分佈式 Map,如 Hazelcast。讓 Spring Session 支持某種數據存儲是至關容易的,如今有支持各類數據存儲的社區實現。

Spring Session 對 HTTP 的支持

Spring Session 對 HTTP 的支持是經過標準的 servlet filter 來實現的,這個 filter 必需要配置爲攔截全部的 web 應用請求,而且它應該是 filter 鏈中的第一個 filter。Spring Session filter 會確保隨後調用javax.servlet.http.HttpServletRequestgetSession()方法時,都會返回 Spring Session 的HttpSession實例,而不是應用服務器默認的 HttpSession。

若是要理解它的話,最簡單的方式就是查看 Spring Session 實際所使用的源碼。首先,咱們瞭解一下標準 servlet 擴展點的一些背景知識,在實現 Spring Session 的時候會使用這些知識。

在 2001 年,Servlet 2.3 規範引入了ServletRequestWrapper它的javadoc 文檔這樣寫道ServletRequestWrapper「提供了ServletRequest接口的便利實現,開發人員若是但願將請求適配到 Servlet 的話,能夠編寫它的子類。這個類實現了包裝(Wrapper)或者說是裝飾(Decorator)模式。對方法的調用默認會經過包裝的請求對象來執行」。以下的代碼樣例抽取自 Tomcat,展示了 ServletRequestWrapper 是如何實現的。

public class ServletRequestWrapper implements ServletRequest {
 
    private ServletRequest request;
 
    /**
     * 建立 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);
    }
 
    // 爲了保證可讀性,其餘的方法刪減掉了 
}

 

Servlet 2.3 規範還定義了HttpServletRequestWrapper,它是ServletRequestWrapper的子類,可以快速提供HttpServletRequest的自定義實現,以下的代碼是從 Tomcat 抽取出來的,展示了HttpServletRequesWrapper類是如何運行的。

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 中與 session 相關的方法。
 */
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 項目再也不將調用委託給 
   * 應用服務器,而是實現本身的邏輯,
   * 返回由外部數據存儲做爲支撐的 HttpSession 實例。
   *
   * 基本的實現是,先檢查是否是已經有 session 了。若是有的話,
   * 就將其返回,不然的話,它會檢查當前的請求中是否有 session id。
   * 若是有的話,將會根據這個 session id,從它的 SessionRepository 中加載 session。
   * 若是 session repository 中沒有 session,或者在當前請求中,
   * 沒有當前 session id 與請求關聯的話,
   * 那麼它會建立一個新的 session,並將其持久化到 session repository 中。
   */
   @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 定義了SessionRepositoryFilter,它實現了 Servlet Filter接口。我抽取了這個 filter 的關鍵部分,將其列在下面的代碼片斷中,我還添加了一些註釋,用來在本文中闡述這些代碼,因此,一樣的,請閱讀下面代碼的註釋部分。

/*
 * SessionRepositoryFilter 只是一個標準的 ServletFilter,
 * 它的實現擴展了一個 helper 基類。
 */
public class SessionRepositoryFilter < S extends ExpiringSession >
    extends OncePerRequestFilter {
 
    /*
     * 這個方法是魔力真正發揮做用的地方。這個方法建立了 
     * 咱們上文所述的封裝請求對象和 
     * 一個封裝的響應對象,而後調用其他的 filter 鏈。
     * 這裏,關鍵在於當這個 filter 後面的應用代碼執行時,
     * 若是要得到 session 的話,獲得的將會是 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();
        }
    }
}

 

咱們從這一章節獲得的關鍵信息是,Spring Session 對 HTTP 的支持所依靠的是一個簡單老式的ServletFilter,藉助 servlet 規範中標準的特性來實現 Spring Session 的功能。所以,咱們可以讓已有的 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 filter 添加到 web 應用的配置中
  • 配置 Spring Session 如何選擇 session 數據存儲的鏈接

Spring Session 自帶了對 Redis 的支持。搭建和安裝 redis 的細節能夠參考該地址

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

<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 filter 的配置,能夠經過 Spring Boot 的自動配置來實現,這隻須要在 Spring Boot 的配置類上使用 @EnableRedisHttpSession註解就能夠了,以下面的代碼片斷所示。

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

 

至於 Spring Session 到 Redis 鏈接的配置,能夠添加以下配置到 Spring Boot 的 application.properties 文件中:

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

 Spring Boot 提供了大量的基礎設施用來配置到 Redis 的鏈接,定義到 Redis 數據庫鏈接的各類方式均可以用在這裏。你能夠參考該地址的逐步操做指南,來了解如何使用Spring Session 和Spring Boot。

在傳統的 web 應用中,能夠參考該指南來了解如何經過web.xml 來使用Spring Session。

在傳統的 war 文件中,能夠參考該指南來了解如何不使用web.xml 進行配置。

默認狀況下,Spring Session 會使用 HTTP cookie 來存儲 session id,可是咱們也能夠配置 Spring Session 使用自定義的 HTTP header 信息,如x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3,當構建 REST API 的時候,這種方式是頗有用的。完整的指南能夠參考該地址

使用 Spring Session

Spring Session 配置完成以後,咱們就可使用標準的 Servlet API 與之交互了。例如,以下的代碼定義了一個 servlet,它使用標準的 Servlet session API 來訪問 session。

@WebServlet("/example")
public class Example extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
 
    // 使用正常的 servlet API 獲取 session,在底層,
    // session 是經過 Spring Session 獲得的,而且會存儲到 Redis 或 
    // 其餘你所選擇的數據源中 
 
    HttpSession session = request.getSession();
    String value = session.getAttribute("someAttribute");
 
  }
}

 

每一個瀏覽器多個 Session

Spring Session 會爲每一個用戶保留多個 session,這是經過使用名爲「_s」的 session 別名參數實現的。例如,若是到達的請求爲 http://example.com/doSomething?_s=0 ,那麼 Spring Session 將會讀取「_s」參數的值,並經過它肯定這個請求所使用的是默認 session。

若是到達的請求是 http://example.com/doSomething?_s=1的話,那麼 Spring Session 就能知道這個請求所要使用的 session 別名爲 1. 若是請求沒有指定「_s」參數的話,例如 http://example.com/doSomething,那麼 Spring Session 將其視爲使用默認的 session,也就是說_s=0

要爲某個瀏覽器建立新的 session,只須要調用javax.servlet.http.HttpServletRequest.getSession()就能夠了,就像咱們一般所作的那樣,Spring Session 將會返回正確的 session 或者按照標準 Servlet 規範的語義建立一個新的 session。下面的表格描述了針對同一個瀏覽器窗口,getSession()面對不一樣 url 時的行爲。

HTTP 請求 URL

Session 別名

getSession() 的行爲

example.com/resource

 

若是存在 session 與別名 0 關聯的話,就返回該 session,不然的話建立一個新的 session 並將其與別名 0 關聯。

example.com/resource?_s=1

1

若是存在 session 與別名 1 關聯的話,就返回該 session,不然的話建立一個新的 session 並將其與別名 1 關聯。

example.com/resource?_s=0

 

若是存在 session 與別名 0 關聯的話,就返回該 session,不然的話建立一個新的 session 並將其與別名 0 關聯。

example.com/resource?_s=abc

abc

若是存在 session 與別名 abc 關聯的話,就返回該 session,不然的話建立一個新的 session 並將其與別名 abc 關聯。

如上面的表格所示,session 別名不必定必須是整型,它只須要區別於其餘分配給用戶的 session 別名就能夠了。可是,整型的 session 別名多是最易於使用的,Spring Session 提供了HttpSessionManager接口,這個接口包含了一些使用 session 別名的工具方法。

咱們能夠在HttpServletRequest中,經過名爲「org.springframework.session.web.http.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 的 key
     * 得到 Spring Session session 管理器的引用 
     */
 
    HttpSessionManager sessionManager=(HttpSessionManager)request.getAttribute(
        "org.springframework.session.web.http.HttpSessionManager");
 
    /*
     * 使用 session 管理器找出所請求 session 的別名。
     * 默認狀況下,session 別名會包含在 url 中,而且請求參數的名稱爲「_s」。
     * 例如,http://localhost:8080/example?_s=1
     * 將會使以下的代碼打印出「Requested Session Alias is: 1」
     */
    String requestedSessionAlias=sessionManager.getCurrentSessionAlias(request);
    System.out.println("Requested Session Alias is:  " + requestedSessionAlias);
 
    /* 返回一個惟一的 session 別名 id,這個別名目前沒有被瀏覽器用來發送請求。
     * 這個方法並不會建立新的 session,
     * 咱們須要調用 request.getSession() 來建立新 session。
     */
    String newSessionAlias = sessionManager.getNewSessionAlias(request);
 
    /* 使用新建立的 session 別名來創建 URL,這個 URL 將會包含 
     * 「_s」參數。例如,若是 newSessionAlias 的值爲 2 的話, 
     * 那麼以下的方法將會返回「/inbox?_s=2」
     */
 
    String encodedURL = sessionManager.encodeURL("/inbox", newSessionAlias);
    System.out.println(encodedURL);
 
    /* 返回 session 別名與 session id 所組成的 Map,
    * 它們是由瀏覽器發送請求所造成的。
     */
    Map < String, String > sessionIds = sessionManager.getSessionIds(request);
  }
}

 

結論

Spring Session 爲企業級 Java 的 session 管理帶來了革新,使得以下的任務變得更加容易:

  • 編寫可水平擴展的原生雲應用。
  • 將 session 所保存的狀態卸載到特定的外部 session 存儲中,如 Redis 或 Apache Geode 中,它們可以以獨立於應用服務器的方式提供高質量的集羣。
  • 當用戶使用 WebSocket 發送請求的時候,可以保持 HttpSession 處於活躍狀態。
  • 在非 Web 請求的處理代碼中,可以訪問 session 數據,好比在 JMS 消息的處理代碼中。
  • 支持每一個瀏覽器上使用多個 session,這樣就能夠很容易地構建更加豐富的終端用戶體驗。
  • 控制客戶端和服務器端之間如何進行 session id 的交換,這樣更加易於編寫 Restful API,由於它能夠從 HTTP 頭信息中獲取 session id,而沒必要再依賴於 cookie。

若是你想拋棄傳統的重量級應用服務器,但受制於已經使用了這些應用服務器的 session 集羣特性,那麼 Spring Session 將是幫助你邁向更加輕量級容器的重要一步,這些輕量級的容器包括 Tomcat、Jetty 或 Undertow。

相關文章
相關標籤/搜索