連接無狀態
使用 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
會話安全問題
關於會話安全問題,由於了解知識尚淺,暫且不做探討,后續會補充該部分。