SpringSession:整合SpringBoot
springSession
是 spring
旗下的一個專案,把 servlet
容器實現的 httpSession
替換為 springSession
,專注於解決 session
管理問題。可簡單快速且無縫的整合到我們的應用中。本文通過一個案例,使用 SpringBoot
來整合 SpringSession
,並且使用 Redis
作為儲存來實踐下 SpringSession
的使用。
環境準備
因為需要使用 Redis
作為底層 Session
的儲存介質,實現分散式 session
,因此需要安裝 Redis
。
Redis 安裝
1、從官網下載最新版的 Redis

2、解壓
tar zxvf redis-5.0.0.tar.gz 複製程式碼
3、編譯測試
sudo make test 複製程式碼
4、編譯安裝
sudo make install 複製程式碼
5、安裝問題
如果您之前安裝過,重複安裝且沒有解除安裝乾淨的話,會報下面的錯
make[1]: *** [test] Error 1 make: *** [test] Error 2 複製程式碼
解決這個錯誤,執行下面的語句即可:
make distclean make make test 複製程式碼
正確安裝姿勢如下:

6、啟動 Redis
在您的 Redis
安裝目錄下,有 redis-server
,執行該指令碼命令:

OK,到這裡, Redis
的安裝工作完畢。
SpringBoot 工程準備
這裡我們直接通過 Idea
來構建我們的 SpringBoot
工程。
File->New->Project : Spring Initializr 複製程式碼

OK, SpringBoot
工程準備完畢,這裡選擇建立的是一個 Web
工程。
整合
整合主要是依賴引入,這裡需要 redis
和 session
的依賴
依賴引入
<dependencies> <!--redis 依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--sessions 依賴--> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> </dependencies> 複製程式碼
配置application.properties
#服務埠 server.port=8080 #redi主機地址 spring.redis.host=localhost #redis服務埠 spring.redis.port=6379 # spring session使用儲存型別,spirngboot預設就是使用redis方式,如果不想用可以填none。 spring.session.store-type=redis 複製程式碼
在啟動類中加入@EnableRedisHttpSession 註解
@SpringBootApplication @EnableRedisHttpSession public class SpringBootSessionApplication { public static void main(String[] args) { SpringApplication.run(SpringBootSessionApplication.class, args); } } 複製程式碼
測試
先來編寫一個 Controller
/** * SessionController * * @author: glmapper@leishu * @since: 18/11/3 下午3:16 * @version 1.0 **/ @Controller @RequestMapping(value = "/") public class SessionController { @ResponseBody @RequestMapping(value = "/session") public Map<String, Object> getSession(HttpServletRequest request) { request.getSession().setAttribute("userName", "glmapper"); Map<String, Object> map = new HashMap<>(); map.put("sessionId", request.getSession().getId()); return map; } @ResponseBody @RequestMapping(value = "/get") public String get(HttpServletRequest request) { String userName = (String) request.getSession().getAttribute("userName"); return userName; } } 複製程式碼
測試結果
啟動 SpringBoot
工程;然後瀏覽器中輸入地址 ofollow,noindex">http://localhost:8080/session;

Controller
中的第一個方法
getSession
,這個方法向
session
中設定了一個值。
下面我們執行: http://localhost:8080/get 這裡是從 session
中取值:

到此, SpringBoot
整合 SpringSession
的過程就完成了。這裡我們只是引入了依賴,然後做了簡單的配置,那麼我們的請求是如何被 SpringSession
處理的呢?從我們一貫的認知來看,對於基於 Servlet
規範的容器( SpringBoot
使用的是嵌入式 Tomcat
)的應用,請求最先被處理的是 Filter
。我們在基於 Spring+SpringMvc
這套技術棧開發時,如果我們需要做許可權管理,通過會基於 Filter
或者攔截器。但是這裡貌似我們什麼也沒做,但是請求確實被 SpringSession
處理了。OK,我們來扒一扒。
SpringSession 是如何處理請求的?

SpringBoot
的啟動日誌;上圖紅色框內的是當前應用註冊是
Filter
資訊,從這裡可以看到有個和
session
有關的
Filter:sessionRepositoryFilter
;這個
bean
對應的類是:
org.springframework.boot.autoconfigure.session.SessionRepositoryFilterConfiguration.ConditionalOnBean= org.springframework.session.web.http.SessionRepositoryFilter 複製程式碼
在這裡找到了

這裡涉及到 SpringBoot
的自動配置,從 spring-boot-autoconfig
包下載入 spring-autoconfigure-metadata.properties
配置檔案,然後獲取所有支援自動配置的資訊; SpringSession
也在其中。關於如何載入並且註冊不在本文的範疇之內,我們繼續來分析 SpringSession
的處理過程。
SpringSession 的處理過程
從上面 SpringBoot
的啟動過程我們找到了處理 session
的 Filter
,然後知道了它是通過自動配置的方式被註冊到當前的容器並且來處理請求。
@Order(SessionRepositoryFilter.DEFAULT_ORDER) public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter { 複製程式碼
從 SessionRepositoryFilter
的定義來看:
- 1、使用了
Order
,並且配置了一個很小的值(Integer.MIN_VALUE + 50
),以此來確保session
的Filter
在Filter
鏈中被優先執行。 - 2、集成了
OncePerRequestFilter
,確保在一次請求只通過一次filter
,而不需要重複執行
為什麼 session
的 Filter
要被優先執行呢?因為我們的請求被包裝了,如果 SessionRepositoryFilter
不優先處理請求,可能會導致後續的請求行為不一致,這裡涉及到 springSession
無縫替換應用伺服器的 request
的原理:
- 1.自定義個
Filter
,實現doFilter
方法 - 2.繼承
HttpServletRequestWrapper
、HttpServletResponseWrapper
類,重寫getSession
等相關方法(在這些方法裡呼叫相關的session
儲存容器操作類)。 - 3.自定義
request
和response
類;並把它們分別傳遞到過濾器鏈 - 4.把該
filter
配置到過濾器鏈的第一個位置上
OK,瞭解了這些背景,我們來跟蹤下整個處理流程。
1、斷點到 doFilterInternal

從這裡可以看到 request
和 response
類被包裝了。
2、斷點到 getSession
這裡是從 Redis
中拿我們 session
資料的地方

-
先從我們當前
servlet
容器中去拿,如果拿到則直接返回 -
去
Redis
中取Reids
中去查一次,避免一次與Reids
的互動。- 如果快取當前應用容器快取中有,則直接返回當前被快取的
session
- 如果沒有,則從請求中獲取
sessionId
,並且根據當前sessionId
去Reids
中查詢session
資料 - 更新快取
session,sessionId,requestedSessionCached
等資料狀態
- 如果快取當前應用容器快取中有,則直接返回當前被快取的
-
如果
Redis
中有,則更新session
相關資訊並返回 -
如果
Reids
中沒有找到,則根據create
來判斷是否建立新的session
。
斷點到 readCookieValues
SpringSession
提供了兩種儲存和傳遞 SessionId
的方式,一種是基於 Cookie
的,一種是基於 Header
的。 SpringSession
中預設使用的是基於 Cookie
的方式。 readCookieValues
就是實現如何從 Cookie
中獲取 sessionId
的。

這個過程其實很簡單,先是從 request
中獲取當前請求攜帶的所以的 Cookie
資訊,然後將匹配到的 cookieName
為 “SESSION”
的 Cookie
進行解析。
斷點到 RedisOperationsSessionRepository -> getSession
這裡是從 Redis
中取 session
資料的地方

- 根據
sessionId
從Redis
中取到entries
資料 - 構建
RedisSession
並返回
斷點到 commitSession
commitSession
作用是通過 HttpSessionIdResolver
將 sessionId
寫到 response
,並且進行持久化。

這裡的 session
其實是已經更新過狀態的,比如重新設定了 session
的過期時間等。 session
提交實際上就意味著當前請求已經處理完畢了。