SpringSession系列-整合SpringBoot

glmapper發表於2018-11-03

springSessionspring 旗下的一個專案,把 servlet 容器實現的 httpSession替換為springSession,專注於解決session管理問題。可簡單快速且無縫的整合到我們的應用中。本文通過一個案例,使用SpringBoot來整合 SpringSession,並且使用Redis作為儲存來實踐下SpringSession 的使用。

環境準備

因為需要使用Redis作為底層Session的儲存介質,實現分散式session,因此需要安裝Redis

Redis 安裝

1、從官網下載最新版的Redis

SpringSession系列-整合SpringBoot

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
複製程式碼

正確安裝姿勢如下:

SpringSession系列-整合SpringBoot

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

SpringSession系列-整合SpringBoot

OK,到這裡,Redis的安裝工作完畢。

SpringBoot 工程準備

這裡我們直接通過Idea來構建我們的SpringBoot工程。

File->New->Project : Spring Initializr
複製程式碼

SpringSession系列-整合SpringBoot

OK,SpringBoot 工程準備完畢,這裡選擇建立的是一個Web工程。

整合

整合主要是依賴引入,這裡需要redissession的依賴

依賴引入

<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;

SpringSession系列-整合SpringBoot
這裡對應執行的是我們上面Controller中的第一個方法getSession,這個方法向session中設定了一個值。

下面我們執行:http://localhost:8080/get 這裡是從session中取值:

SpringSession系列-整合SpringBoot

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

SpringSession 是如何處理請求的?

SpringSession系列-整合SpringBoot
上面這張截圖想必大家都不陌生,是SpringBoot的啟動日誌;上圖紅色框內的是當前應用註冊是Filter資訊,從這裡可以看到有個和 session 有關的Filter:sessionRepositoryFilter;這個bean對應的類是:

org.springframework.boot.autoconfigure.session.SessionRepositoryFilterConfiguration.ConditionalOnBean=
org.springframework.session.web.http.SessionRepositoryFilter
複製程式碼

在這裡找到了

SpringSession系列-整合SpringBoot

這裡涉及到SpringBoot的自動配置,從spring-boot-autoconfig包下載入spring-autoconfigure-metadata.properties 配置檔案,然後獲取所有支援自動配置的資訊;SpringSession 也在其中。關於如何載入並且註冊不在本文的範疇之內,我們繼續來分析SpringSession的處理過程。

SpringSession 的處理過程

從上面SpringBoot的啟動過程我們找到了處理sessionFilter,然後知道了它是通過自動配置的方式被註冊到當前的容器並且來處理請求。

@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> 
extends OncePerRequestFilter {
複製程式碼

SessionRepositoryFilter的定義來看:

  • 1、使用了Order,並且配置了一個很小的值(Integer.MIN_VALUE + 50),以此來確保sessionFilterFilter鏈中被優先執行。
  • 2、整合了OncePerRequestFilter,確保在一次請求只通過一次filter,而不需要重複執行

為什麼 sessionFilter 要被優先執行呢?因為我們的請求被包裝了,如果SessionRepositoryFilter不優先處理請求,可能會導致後續的請求行為不一致,這裡涉及到 springSession無縫替換應用伺服器的request的原理:

  • 1.自定義個Filter,實現doFilter方法
  • 2.繼承 HttpServletRequestWrapperHttpServletResponseWrapper 類,重寫getSession等相關方法(在這些方法裡呼叫相關的 session儲存容器操作類)。
  • 3.自定義requestresponse類;並把它們分別傳遞到過濾器鏈
  • 4.把該filter配置到過濾器鏈的第一個位置上

OK,瞭解了這些背景,我們來跟蹤下整個處理流程。

1、斷點到 doFilterInternal

SpringSession系列-整合SpringBoot

從這裡可以看到requestresponse類被包裝了。

2、斷點到 getSession

這裡是從Redis中拿我們session資料的地方

SpringSession系列-整合SpringBoot

  • 先從我們當前servlet容器中去拿,如果拿到則直接返回

  • Redis中取

    SpringSession系列-整合SpringBoot
    這裡會有一個快取處理,並非是每次都到Reids中去查一次,避免一次與Reids的互動。

    • 如果快取當前應用容器快取中有,則直接返回當前被快取的session
    • 如果沒有,則從請求中獲取sessionId,並且根據當前sessionIdReids中查詢session資料
    • 更新快取session,sessionId,requestedSessionCached等資料狀態
  • 如果Redis中有,則更新session相關資訊並返回

  • 如果Reids中沒有找到,則根據 create 來判斷是否建立新的session

斷點到 readCookieValues

SpringSession提供了兩種儲存和傳遞SessionId的方式,一種是基於Cookie的,一種是基於Header的。SpringSession中預設使用的是基於Cookie的方式。readCookieValues 就是實現如何從Cookie中獲取sessionId的。

SpringSession系列-整合SpringBoot

這個過程其實很簡單,先是從request中獲取當前請求攜帶的所以的Cookie資訊,然後將匹配到的 cookieName“SESSION”Cookie進行解析。

斷點到 RedisOperationsSessionRepository -> getSession

這裡是從Redis中取session資料的地方

SpringSession系列-整合SpringBoot

  • 根據sessionIdRedis中取到 entries 資料
  • 構建 RedisSession 並返回

斷點到 commitSession

commitSession作用是通過HttpSessionIdResolversessionId寫到response,並且進行持久化。

SpringSession系列-整合SpringBoot

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

小結

本文先介紹瞭如何使用 SpringBoot 整合 SpringSession,並且以 Redis 作為儲存。然後簡單分析了 SpringSession 的處理過程,本文對 SpringSession 的原理部分沒有進行深入分析,下一篇分析下SpringSession的原理。

相關文章