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
工程;然後瀏覽器中輸入地址 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
提交實際上就意味著當前請求已經處理完畢了。
小結
本文先介紹瞭如何使用 SpringBoot
整合 SpringSession
,並且以 Redis
作為儲存。然後簡單分析了 SpringSession
的處理過程,本文對 SpringSession
的原理部分沒有進行深入分析,下一篇分析下SpringSession
的原理。