Spring Session的使用示例

Session

Http協議是無狀態的,這樣對於服務端來說,沒有辦法區分是新的訪客還是舊的訪客。但是,有些業務場景,需要追蹤用戶多個請求,此時就需要Session。關於session的百度百科session

Session:在計算機中,尤其是在網絡應用中,稱為“會話控制”。Session對象存儲特定用戶會話所需的屬性及配置信息。這樣,當用戶在應用程序的Web頁之間跳轉時,存儲在Session對象中的變量將不會丟失,而是在整個用戶會話中一直存在下去。當用戶請求來自應用程序的 Web頁時,如果該用戶還沒有會話,則Web服務器將自動創建一個 Session對象。當會話過期或被放棄後,服務器將終止該會話

核心特點:

  1. 服務端存儲
  2. 會過期

Session常用解決方案

對於Session的常用解決方案,可以劃分為三種。

  • 負載均衡方式

借助負載均衡設備或者模塊,將指定的Session始終路由到同一臺機器即可,如Nginx。

  • 副本復制方式

利用服務器節點間的副本復制方式,保證集群所有節點擁有的Session數據一致。

  • 集中存儲方式

引入第三方存儲,將Session數據集中存儲到外部存儲中,如Redis或者數據庫等。

本文介紹的Spring-Session是采用第三種,集中存儲的方式。

Spring-Session

核心組成模塊

  • Spring Session Core

提供Spring Session核心的功能和API

  • Spring Session Data Redis

提供基於Redis的SessionRepository以及配置

  • Spring Session JDBC

提供基於關系型數據庫的SessionRepository以及配置

  • Spring Session Hazelcast

提供基於Hazelcast的SessionRepository以及配置

測試代碼

controller提供三個接口,分別對應Session的獲取、保存和清理

@GetMapping("/")
public String process(Model model, HttpSession session) {
  @SuppressWarnings("unchecked")
  List<String> messages = (List<String>) session.getAttribute("springSession");

  if (messages == null) {
    messages = new ArrayList<>();
  }
  model.addAttribute("sessionMessages", messages);

  return "sessionTest";
}

@PostMapping("/persistSession")
public String persistMessage(@RequestParam("msg") String msg, HttpServletRequest request) {
  @SuppressWarnings("unchecked")
  List<String> messages = (List<String>) request.getSession().getAttribute("springSession");
  if (messages == null) {
    messages = new ArrayList<>();
    request.getSession().setAttribute("springSession", messages);
  }
  messages.add(msg);
  request.getSession().setAttribute("springSession", messages);
  return "redirect:/";
}

@PostMapping("/destroySession")
public String destroySession(HttpServletRequest request) {
  request.getSession().invalidate();
  return "redirect:/";
}

sessionTest.html對應頁面操作

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Spring Boot Session Example</title>
</head>
<body>
<div>
    <form th:action="@{/persistSession}" method="post">
        <textarea name="msg" cols="40" rows="2"></textarea>
        <br> <input type="submit" value="保存" />
    </form>
</div>
<div>
    <h2>session列表</h2>
    <ul th:each="message : ${sessionMessages}">
        <li th:text="${message}">message</li>
    </ul>
</div>

<div>
    <form th:action="@{/destroySession}" method="post">
        <input type="submit" value="清空" />
    </form>
</div>
</body>
</html>

基於數據庫的Spring-Session

1.引入maven依賴

使用MySQL存儲,所以引入瞭MySQL。

涉及到SpringBoot JDBC的配置,引入瞭Spring Boot JDBC Starter。

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-core</artifactId>
  <version>2.5.0</version>
</dependency>

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-jdbc</artifactId>
  <version>2.5.0</version>
</dependency>

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.18</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

註意:

No session repository could be auto-configured, check your configuration (session store type is ‘jdbc’)

如果存在這個報錯,是因為沒有引入spring-boot-starter-jdbc,引入即可。

2.配置application.properties文件

主要包含兩部分,數據庫的配置以及Spring Session Jdbc配置。

# 配置數據源相關內容
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/spring_learn?createDatabaseIfNotExist=true&autoReconnect=true&useSSL=false
spring.datasource.username=xxx
spring.datasource.password=xxx
spring.datasource.initialization-mode=always

# session類型選擇jdbc
spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=always
# 指定表名
#spring.session.jdbc.table-name=SESSIONS
# 超時時間
spring.session.timeout=180s

3.數據庫存儲解析

默認情況下,數據庫中會創建2張表。SPRING_SESSION和SPRING_SESSION_ATTRIBUTION。

SPRING_SESSION用於存在session自身的一些屬性,如創建時間、過期時間等,詳細schema如下。

CREATE TABLE `SPRING_SESSION` (
  `PRIMARY_ID` char(36) NOT NULL,
  `SESSION_ID` char(36) NOT NULL,
  `CREATION_TIME` bigint NOT NULL,
  `LAST_ACCESS_TIME` bigint NOT NULL,
  `MAX_INACTIVE_INTERVAL` int NOT NULL,
  `EXPIRY_TIME` bigint NOT NULL,
  `PRINCIPAL_NAME` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`PRIMARY_ID`),
  UNIQUE KEY `SPRING_SESSION_IX1` (`SESSION_ID`),
  KEY `SPRING_SESSION_IX2` (`EXPIRY_TIME`),
  KEY `SPRING_SESSION_IX3` (`PRINCIPAL_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC

SPRING_SESSION_ATTRIBUTION用於存儲session相關聯的屬性,schema如下。

CREATE TABLE `SPRING_SESSION_ATTRIBUTES` (
  `SESSION_PRIMARY_ID` char(36) NOT NULL,
  `ATTRIBUTE_NAME` varchar(200) NOT NULL,
  `ATTRIBUTE_BYTES` blob NOT NULL,
  PRIMARY KEY (`SESSION_PRIMARY_ID`,`ATTRIBUTE_NAME`),
  CONSTRAINT `SPRING_SESSION_ATTRIBUTES_FK` FOREIGN KEY (`SESSION_PRIMARY_ID`) REFERENCES `SPRING_SESSION` (`PRIMARY_ID`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC

4.測試執行

SPRING_SESSION中的數據

SPRING_SESSION_ATTRIBUTION中的數據。

基於Redis的Spring-Session

幾乎同樣的步驟

maven依賴

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>

application.properties配置

spring.session.store-type=redis
spring.redis.host=127.0.0.1
spring.redis.port=6379

結果分析

一次請求後,多瞭三個屬性,分析如下。

key 類型 用途 value
spring:session:sessions:expires:${sessionId} string 判斷sesssion是否存在
spring:session:sessions:${sessionId} hash session相關的屬性,包括有效期、創建時間、具體屬性等 creationTime/lastAccessedTime/sessionAttr/maxInactiveInterval
spring:session:expirations:1623656160000 set 存儲待過期的sessionId列表 key: 過期的時間戳;value: 在這個時間戳將要過期的expire key列表。

在訪問時,先通過第一個key,判斷session是否存在以及是否過期。如果沒有過期,可以通過第二個key獲取或者更新對應的session詳情。

對於第三個key,實際上Spring-Session-Redis會有特殊的用途,主要是為瞭Redis的keySpace-notificationhttps://redis.io/topics/notifications。核心目的是為瞭確保過期的session一定要觸發過期事件。關於這方面的解釋,可以看一下RedisIndexedSessionRepository中的註釋。

訂閱Spring-Session的相關事件

有些時候,我們比較關心Session的創建或者銷毀事件,做一些特殊的處理或者記錄。基於Redis的Spring-Session利用Spring Event將該事件發佈出來,我們可以使用EventListener監聽做處理。

@Component
@Slf4j
public class AnnotationDrivenEventListener {

  @EventListener
  public void handleSessionCreated(SessionCreatedEvent sessionCreatedEvent) {
    String sessionId = sessionCreatedEvent.getSessionId();
    log.info("session id:{} created", sessionId);
  }

  @EventListener
  public void handleSessionDestroyed(SessionDestroyedEvent sessionDestroyedEvent) {
    String sessionId = sessionDestroyedEvent.getSessionId();
    log.info("session id:{} destroyed", sessionId);
  }
}

總結

Spring Session提供瞭非常便利的,基於關系型數據庫或者Redis的Session解決方案。

Redis版訪問速度快,基於Redis的過期策略,保證過期數據會被刪除,同時支持事件訂閱。

數據庫版直接基於數據庫,無需單獨引入其他存儲。但是訪問速度相對較慢,過期數據需要依賴應用程序自身進行刪除。同時沒有提供事件訂閱能力。

以上就是Spring Session的使用示例的詳細內容,更多關於Spring Session的使用的資料請關註WalkonNet其它相關文章!

推薦閱讀: