1. 程式人生 > >[轉] Spring的session管理

[轉] Spring的session管理

nts pan filter [] 無需 多級 true 消息處理 eas

Session管理是企業級Java中的一部分。隨著現在的趨勢朝著微服務以及可水平擴展的原生雲應用發展,傳統的session管理逐漸暴露出自己的不足。

本文闡述spring session API如何革新過去的session管理的不足。先闡述一下當前session管理中的問題,然後深入介紹Spring session如何解決這些問題的。最後,將會詳細展示Spring session如何運行的,及在項目中怎樣使用它。

Spring session帶來的新功能如下,使得如下功能更加容易實現:

1. 編寫可水平擴展的原生雲應用。

2. 將session所保存的狀態卸載到特定的外部session存儲中,如redis或apache geode中,它們能夠以獨立於應用服務器的方式提供高質量的集群。

3. 當用戶使用websocket發送請求的時候,能夠保持HttpSession處於活躍狀態。

4. 在非Web請求的處理代碼中,能夠訪問session數據,比如在非JMS消息的處理代碼中。

5. 支持每個瀏覽器上使用多個session, 從而能夠很容易地構建更加豐富的終端用戶體驗。

6. 控制session id如何在客戶端和服務器之間進行交換,這樣的話就能很容易地編寫Restful API, 因為它 可以從HTTP頭信息中獲取session id, 而不必在依賴於cookie。

7. 需要說明的很重要的一點是,spring session的核心項目並不依賴於spring框架,所以,我們甚至能夠將其應用於不使用spring框架的項目中。

一、傳統session管理的問題

傳統的javaEE Session管理會有各種問題,下面以樣例的形式進行闡述。

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

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

在很多的應用服務器中,都會將http session狀態保存在jvm中,這個jvm與運行應用程序代碼的jvm是同一個,因為這樣易於實現,並且速度很快。當新的應用服務器實例加入或離開集群時,http session會基於現有的應用服務器實例進行重新平衡。在彈性的雲環境中,我們會用友上百個應用服務器實例,切實例的數量可能在任意時刻增加或減少,這樣的話,我們就會遇到一些問題:

重平衡http session可能會成為性能評頸。

為了存儲大量的session,會需要很大的堆空間,這會導致垃圾收集,從而對性能產生負面影響。

雲基礎設施通常會禁止TCP多播,但是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應用,在這個應用中有些用戶會創建多個賬號。例如,[email protected], [email protected] 和其他java Web應用一樣,我們會使用httpsession來跟蹤應用的狀態,如當前登錄的用戶。所以,[email protected]@example.com時,他必須要首先退出,然後再重新登陸回來。

借助spring session,為每個用戶配置多個http session會非常容易,這樣用戶在兩個賬戶之間切換的時候,就不需要退出和重新登錄了。

多級別的安全預覽

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

例如,假設應用有4個安全級別:public、confidential、secret和top secret. 當用戶登錄應用之後,系統會判斷用戶所具有的最高安全級別並且只會顯示該級別和該級別之下的數據。所以,具有public權限的用戶只能看到public級別的文檔,具有secret權限的用戶能夠看到public、confidentail和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應用,那麽他們可以使用H5上的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, 所以我們必須要以一種自定義的方案來獲取httpsession中的數據,這要通過自定義的機制來實現。

通過使用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.HttpServletRequest的getSession()方法時,都會返回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的方式是手動完成上述的每一個配置步驟

借助像Maven或Gradle這樣的依賴管理器,將Spring Session添加應用中是很容易的。如果你使用Maven和Spring Boot的話,那麽可以在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 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();

}
}

每個瀏覽器多個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

0

如果存在session與別名0關聯的話,就返回該session,否則的話創建一個新的session並將其與別名0關聯。

example.com/resource?_s=1

1

如果存在session與別名1關聯的話,就返回該session,否則的話創建一個新的session並將其與別名1關聯。

example.com/resource?_s=0

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[也稱之為password]
* 獲得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。

-------------------------------------

[轉] Spring的session管理