連接無狀態
使用 HTTP 的連接是無狀態的,因此為了應對需要狀態的服務例如用戶登錄,誕生了適合保存狀態的設計-會話(session),本文就來探討一下會話。
會話的使用
Spring Mvc 中使用會話很簡單,在控制器類的方法參數列表中,直接編寫 HttpSession 類型的參數,或者參數列表中編寫 HttpServletRequest 類,然后使用 getSession() 方法獲取會話。
下面是使用會話的簡單例子,第一次訪問時會創建一個無數據的會話,因此獲取到的 access 屬性為 null ,而當不是第一次訪問時,由於屬性不為 null 會得到 "NOT THE FIRST TIME ACCESS" 。
@RestController
public class DemoController {
private static final String ACCESS = "access";
@RequestMapping("/")
public String index(HttpSession session) { // or `index(HttpServletRequest req)`
// then `HttpSession session = request.getSession();`
if (session.getAttribute(ACCESS) == null) {
session.setAttribute(ACCESS, true);
return "FIRST TIME ACCESS";
}
return "NOT THE FIRST TIME ACCESS";
}
}
注意:由於用 Mock Mvc 測試獲取不到第一次請求 Cookies,因此無法模擬得到正確結果,請使用瀏覽器或者請求工具測試。
常用方法
上面例子展示了會話的簡單使用,其中 HttpSession 接口是 servlet 的標准,而 Spring Mvc 中的會話默認使用 Tomcat 的實現。下面來介紹幾個常用方法,更多方法使用請參考這篇文章。
Object getAttribute(String)方法用來獲取會話的屬性,若不存在則返回 nullvoid setAttribute(String, Object)方法用來設置會話的屬性void removeAttribute(String)方法用來刪除會話的屬性void setMaxInactiveInterval(int)方法用來設置會話失效時間,單位為秒,設置小於等於零的數則會話永不過期void invalidate()手動使會話失效並清理會話數據
會話監聽器
會話的生命周期分別為創建、失效和創建與失效之間,而會話監聽器是為了滿足會話生命周期中觸發相應事件的需要,HttpSessionListener 和 HttpSessionAttributeListener 兩個監聽器接口分別滿足了會話的各個生命周期。使用監聽器只需實現這些接口然后標注 @WebListener 注解即可,下面會有實現的例子。
針對會話的監聽器
HttpSessionListener 接口可以算是針對會話的監聽器接口,因為它的兩個方法分別在會話創建和失效時調用,下面為一個簡單的例子,參數列表中 HttpSessionEvent 類可以用 getSession 獲取會話。
@WebListener
public class SessionListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent event) {
// ...
}
public void sessionDestroyed(HttpSessionEvent event) {
// ...
}
}
針對會話屬性的監聽器
與 HttpSessionListener 監聽器接口接管會話生命周期的創建與失效不同,HttpSessionAttributeListener 監聽器接口負責會話屬性的創建、銷毀與替換,下面為該監聽器的簡單例子,參數列表中 HttpSessionBindingEvent 類除了可以用 getSession 獲取會話,最主要的是可用 getName 和 getValue 分別獲取屬性的名字和值。
@WebListener
public class SessionListener implements HttpSessionBindingListener {
public void attributeAdded(HttpSessionBindingEvent event) {
// ...
}
public void attributeRemoved(HttpSessionBindingEvent event) {
// ...
}
public void attributeReplaced(HttpSessionBindingEvent event) {
// ...
}
}
使監聽器生效
上面的例子只是編寫了監聽器的實現,為了使得監聽器在項目里生效,還必須在啟動類或者配置類上標注 @ServletComponentScan 來掃描這些屬於 servlet 組件的監聽器,例如下面在配置類上啟動 servlet 組件掃描。
@Configuration
@ServletComponentScan // enable scan servlet component
public class ApplicationConf {
// ...
}
分布式會話
若是有多台 Web 服務器提供不同的服務,且要求屬於同一會話,上面的單機會話例子就無法滿足要求,於是就有了分布式會話即可以共享會話數據。
利用 Spring Session 就可以實現分布式會話,而 Spring Session 的實現可依賴關系數據庫或內存數據庫,下面例子為 Spring Boot 中導入基於 Redis 實現的 Spring Session 的依賴。
<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>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
接着在 Spring Boot 的屬性配置文件中,添加如下的屬性即可,而對於會話的使用和單機會話操作是一樣的。
spring:
session:
store-type: redis
redis:
host: 127.0.0.1
port: 6379
會話安全問題
關於會話安全問題,由於了解知識尚淺,暫且不做探討,后續會補充該部分。
