Redis 設定
現在您已經設定好應用程式,您可能想要開始自訂一些項目
-
我想要使用 Spring Boot 屬性自訂 Redis 設定
-
我想要關於選擇
RedisSessionRepository
或RedisIndexedSessionRepository
的協助。 -
我想要指定不同的命名空間。
使用 JSON 序列化 Session
預設情況下,Spring Session 使用 Java 序列化來序列化 session 屬性。 有時這可能會產生問題,尤其是在您有多個應用程式使用相同的 Redis 實例,但具有相同類別的不同版本時。 您可以提供 RedisSerializer
bean 以自訂 session 如何序列化到 Redis 中。 Spring Data Redis 提供了 GenericJackson2JsonRedisSerializer
,它使用 Jackson 的 ObjectMapper
序列化和反序列化物件。
@Configuration
public class SessionConfig implements BeanClassLoaderAware {
private ClassLoader loader;
/**
* Note that the bean name for this bean is intentionally
* {@code springSessionDefaultRedisSerializer}. It must be named this way to override
* the default {@link RedisSerializer} used by Spring Session.
*/
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer(objectMapper());
}
/**
* Customized {@link ObjectMapper} to add mix-in for class that doesn't have default
* constructors
* @return the {@link ObjectMapper} to use
*/
private ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModules(SecurityJackson2Modules.getModules(this.loader));
return mapper;
}
/*
* @see
* org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang
* .ClassLoader)
*/
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.loader = classLoader;
}
}
上面的程式碼片段使用了 Spring Security,因此我們正在建立一個自訂的 ObjectMapper
,它使用 Spring Security 的 Jackson 模組。 如果您不需要 Spring Security Jackson 模組,您可以注入應用程式的 ObjectMapper
bean 並像這樣使用它
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(ObjectMapper objectMapper) {
return new GenericJackson2JsonRedisSerializer(objectMapper);
}
指定不同的命名空間
有多個應用程式使用相同的 Redis 實例是很常見的。 因此,Spring Session 使用 namespace
(預設為 spring:session
) 來在需要時保持 session 資料分離。
使用 Spring Boot 屬性
您可以透過設定 spring.session.redis.namespace
屬性來指定它。
spring.session.redis.namespace=spring:session:myapplication
spring:
session:
redis:
namespace: "spring:session:myapplication"
使用註解的屬性
您可以透過在 @EnableRedisHttpSession
、@EnableRedisIndexedHttpSession
或 @EnableRedisWebSession
註解中設定 redisNamespace
屬性來指定 namespace
@Configuration
@EnableRedisHttpSession(redisNamespace = "spring:session:myapplication")
public class SessionConfig {
// ...
}
@Configuration
@EnableRedisIndexedHttpSession(redisNamespace = "spring:session:myapplication")
public class SessionConfig {
// ...
}
@Configuration
@EnableRedisWebSession(redisNamespace = "spring:session:myapplication")
public class SessionConfig {
// ...
}
在 RedisSessionRepository
和 RedisIndexedSessionRepository
之間做選擇
當使用 Spring Session Redis 時,您可能需要選擇 RedisSessionRepository
和 RedisIndexedSessionRepository
。 兩者都是 SessionRepository
介面的實作,用於將 session 資料儲存在 Redis 中。 然而,它們在處理 session 索引和查詢方面有所不同。
-
RedisSessionRepository
:RedisSessionRepository
是一個基本實作,它將 session 資料儲存在 Redis 中,而沒有任何額外的索引。 它使用簡單的鍵值結構來儲存 session 屬性。 每個 session 都會被分配一個唯一的 session ID,並且 session 資料儲存在與該 ID 關聯的 Redis 鍵下。 當需要檢索 session 時,儲存庫會使用 session ID 查詢 Redis 以獲取相關的 session 資料。 由於沒有索引,因此基於 session ID 以外的屬性或條件查詢 session 可能效率低下。 -
RedisIndexedSessionRepository
:RedisIndexedSessionRepository
是一個擴充的實作,它為儲存在 Redis 中的 session 提供索引功能。 它在 Redis 中引入了額外的資料結構,以根據屬性或條件有效地查詢 session。 除了RedisSessionRepository
使用的鍵值結構之外,它還維護額外的索引以實現快速查找。 例如,它可以基於 session 屬性 (如使用者 ID 或上次訪問時間) 建立索引。 這些索引允許基於特定條件有效地查詢 session,從而提高效能並啟用進階的 session 管理功能。 除此之外,RedisIndexedSessionRepository
也支援 session 過期和刪除。
當將 RedisIndexedSessionRepository 與 Redis Cluster 一起使用時,您必須注意它只訂閱叢集中一個隨機 redis 節點的事件,如果事件發生在不同的節點中,這可能會導致某些 session 索引未被清除。 |
設定 RedisSessionRepository
監聽 Session 事件
通常,對 session 事件做出反應很有價值,例如,您可能想要根據 session 生命週期執行某種類型的處理。 為了能夠做到這一點,您必須使用索引儲存庫。 如果您不知道索引儲存庫和預設儲存庫之間的區別,您可以前往此章節。
設定索引儲存庫後,您現在可以開始監聽 SessionCreatedEvent
、SessionDeletedEvent
、SessionDestroyedEvent
和 SessionExpiredEvent
事件。 在 Spring 中,有幾種監聽應用程式事件的方法,我們將使用 @EventListener
註解。
@Component
public class SessionEventListener {
@EventListener
public void processSessionCreatedEvent(SessionCreatedEvent event) {
// do the necessary work
}
@EventListener
public void processSessionDeletedEvent(SessionDeletedEvent event) {
// do the necessary work
}
@EventListener
public void processSessionDestroyedEvent(SessionDestroyedEvent event) {
// do the necessary work
}
@EventListener
public void processSessionExpiredEvent(SessionExpiredEvent event) {
// do the necessary work
}
}
尋找特定使用者的所有 Session
透過檢索特定使用者的所有 session,您可以追蹤使用者在不同裝置或瀏覽器上的活動 session。 例如,您可以使用此資訊進行 session 管理,例如允許使用者使特定 session 無效或登出,或根據使用者的 session 活動執行動作。
要做到這一點,首先您必須使用索引儲存庫,然後您可以注入 FindByIndexNameSessionRepository
介面,如下所示
@Autowired
public FindByIndexNameSessionRepository<? extends Session> sessions;
public Collection<? extends Session> getSessions(Principal principal) {
Collection<? extends Session> usersSessions = this.sessions.findByPrincipalName(principal.getName()).values();
return usersSessions;
}
public void removeSession(Principal principal, String sessionIdToDelete) {
Set<String> usersSessionIds = this.sessions.findByPrincipalName(principal.getName()).keySet();
if (usersSessionIds.contains(sessionIdToDelete)) {
this.sessions.deleteById(sessionIdToDelete);
}
}
在上面的範例中,您可以使用 getSessions
方法來尋找特定使用者的所有 session,並使用 removeSession
方法來移除使用者的特定 session。
設定 Redis Session Mapper
Spring Session Redis 從 Redis 檢索 session 資訊並將其儲存在 Map<String, Object>
中。 此 map 需要經過對應程序才能轉換為 MapSession
物件,然後在 RedisSession
中使用。
用於此目的的預設 mapper 稱為 RedisSessionMapper
。 如果 session map 不包含建構 session 所需的最小必要鍵 (例如 creationTime
),則此 mapper 將會拋出例外。 缺少必要鍵的一個可能情境是當 session 鍵同時被刪除時,通常是由於過期,同時儲存程序正在進行中。 發生這種情況的原因是使用 HSET 命令 來設定鍵內的欄位,如果該鍵不存在,此命令將會建立它。
如果您想自訂對應程序,您可以建立自己的 BiFunction<String, Map<String, Object>, MapSession>
實作並將其設定到 session 儲存庫中。 以下範例示範如何將對應程序委派給預設 mapper,但如果拋出例外,則會從 Redis 中刪除 session
-
RedisSessionRepository
-
RedisIndexedSessionRepository
-
ReactiveRedisSessionRepository
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
@Bean
SessionRepositoryCustomizer<RedisSessionRepository> redisSessionRepositoryCustomizer() {
return (redisSessionRepository) -> redisSessionRepository
.setRedisSessionMapper(new SafeRedisSessionMapper(redisSessionRepository));
}
static class SafeRedisSessionMapper implements BiFunction<String, Map<String, Object>, MapSession> {
private final RedisSessionMapper delegate = new RedisSessionMapper();
private final RedisSessionRepository sessionRepository;
SafeRedisSessionMapper(RedisSessionRepository sessionRepository) {
this.sessionRepository = sessionRepository;
}
@Override
public MapSession apply(String sessionId, Map<String, Object> map) {
try {
return this.delegate.apply(sessionId, map);
}
catch (IllegalStateException ex) {
this.sessionRepository.deleteById(sessionId);
return null;
}
}
}
}
@Configuration
@EnableRedisIndexedHttpSession
public class SessionConfig {
@Bean
SessionRepositoryCustomizer<RedisIndexedSessionRepository> redisSessionRepositoryCustomizer() {
return (redisSessionRepository) -> redisSessionRepository.setRedisSessionMapper(
new SafeRedisSessionMapper(redisSessionRepository.getSessionRedisOperations()));
}
static class SafeRedisSessionMapper implements BiFunction<String, Map<String, Object>, MapSession> {
private final RedisSessionMapper delegate = new RedisSessionMapper();
private final RedisOperations<String, Object> redisOperations;
SafeRedisSessionMapper(RedisOperations<String, Object> redisOperations) {
this.redisOperations = redisOperations;
}
@Override
public MapSession apply(String sessionId, Map<String, Object> map) {
try {
return this.delegate.apply(sessionId, map);
}
catch (IllegalStateException ex) {
// if you use a different redis namespace, change the key accordingly
this.redisOperations.delete("spring:session:sessions:" + sessionId); // we do not invoke RedisIndexedSessionRepository#deleteById to avoid an infinite loop because the method also invokes this mapper
return null;
}
}
}
}
@Configuration
@EnableRedisWebSession
public class SessionConfig {
@Bean
ReactiveSessionRepositoryCustomizer<ReactiveRedisSessionRepository> redisSessionRepositoryCustomizer() {
return (redisSessionRepository) -> redisSessionRepository
.setRedisSessionMapper(new SafeRedisSessionMapper(redisSessionRepository));
}
static class SafeRedisSessionMapper implements BiFunction<String, Map<String, Object>, Mono<MapSession>> {
private final RedisSessionMapper delegate = new RedisSessionMapper();
private final ReactiveRedisSessionRepository sessionRepository;
SafeRedisSessionMapper(ReactiveRedisSessionRepository sessionRepository) {
this.sessionRepository = sessionRepository;
}
@Override
public Mono<MapSession> apply(String sessionId, Map<String, Object> map) {
return Mono.fromSupplier(() -> this.delegate.apply(sessionId, map))
.onErrorResume(IllegalStateException.class,
(ex) -> this.sessionRepository.deleteById(sessionId).then(Mono.empty()));
}
}
}