diff --git a/src/main/java/com/pig4cloud/plugin/cache/MultilevelCacheAutoConfiguration.java b/src/main/java/com/pig4cloud/plugin/cache/MultilevelCacheAutoConfiguration.java index 5704006..221be8d 100755 --- a/src/main/java/com/pig4cloud/plugin/cache/MultilevelCacheAutoConfiguration.java +++ b/src/main/java/com/pig4cloud/plugin/cache/MultilevelCacheAutoConfiguration.java @@ -3,6 +3,7 @@ import com.pig4cloud.plugin.cache.properties.CacheConfigProperties; import com.pig4cloud.plugin.cache.support.CacheMessageListener; import com.pig4cloud.plugin.cache.support.RedisCaffeineCacheManager; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -28,10 +29,13 @@ public class MultilevelCacheAutoConfiguration { @Bean @ConditionalOnBean(RedisTemplate.class) public RedisCaffeineCacheManager cacheManager(CacheConfigProperties cacheConfigProperties, - RedisTemplate stringKeyRedisTemplate) { + @Qualifier("stringKeyRedisTemplate") RedisTemplate stringKeyRedisTemplate) { return new RedisCaffeineCacheManager(cacheConfigProperties, stringKeyRedisTemplate); } + /** + * 可自定义名称为stringKeyRedisTemplate的RedisTemplate覆盖掉默认RedisTemplate。 + */ @Bean @ConditionalOnMissingBean(name = "stringKeyRedisTemplate") public RedisTemplate stringKeyRedisTemplate(RedisConnectionFactory redisConnectionFactory) { @@ -44,11 +48,11 @@ public RedisTemplate stringKeyRedisTemplate(RedisConnectionFacto @Bean public RedisMessageListenerContainer cacheMessageListenerContainer(CacheConfigProperties cacheConfigProperties, - RedisTemplate stringKeyRedisTemplate, RedisCaffeineCacheManager redisCaffeineCacheManager) { + @Qualifier("stringKeyRedisTemplate") RedisTemplate stringKeyRedisTemplate, + RedisCaffeineCacheManager redisCaffeineCacheManager) { RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(stringKeyRedisTemplate.getConnectionFactory()); - CacheMessageListener cacheMessageListener = new CacheMessageListener(stringKeyRedisTemplate, - redisCaffeineCacheManager); + CacheMessageListener cacheMessageListener = new CacheMessageListener(redisCaffeineCacheManager); redisMessageListenerContainer.addMessageListener(cacheMessageListener, new ChannelTopic(cacheConfigProperties.getRedis().getTopic())); return redisMessageListenerContainer; diff --git a/src/main/java/com/pig4cloud/plugin/cache/support/CacheMessageListener.java b/src/main/java/com/pig4cloud/plugin/cache/support/CacheMessageListener.java index 6b98dff..1c07d70 100755 --- a/src/main/java/com/pig4cloud/plugin/cache/support/CacheMessageListener.java +++ b/src/main/java/com/pig4cloud/plugin/cache/support/CacheMessageListener.java @@ -4,7 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; -import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; /** * @author fuwei.deng @@ -14,13 +14,17 @@ @RequiredArgsConstructor public class CacheMessageListener implements MessageListener { - private final RedisTemplate redisTemplate; + private RedisSerializer javaSerializer = RedisSerializer.java(); private final RedisCaffeineCacheManager redisCaffeineCacheManager; @Override public void onMessage(Message message, byte[] pattern) { - CacheMessage cacheMessage = (CacheMessage) redisTemplate.getValueSerializer().deserialize(message.getBody()); + + /** + * 发送端固定了jdk序列户方式,接收端同样固定了jdk序列化方式进行反序列化。 + */ + CacheMessage cacheMessage = (CacheMessage) javaSerializer.deserialize(message.getBody()); log.debug("recevice a redis topic message, clear local cache, the cacheName is {}, the key is {}", cacheMessage.getCacheName(), cacheMessage.getKey()); redisCaffeineCacheManager.clearLocal(cacheMessage.getCacheName(), cacheMessage.getKey()); diff --git a/src/main/java/com/pig4cloud/plugin/cache/support/RedisCaffeineCache.java b/src/main/java/com/pig4cloud/plugin/cache/support/RedisCaffeineCache.java index fc00716..1b1fb5b 100755 --- a/src/main/java/com/pig4cloud/plugin/cache/support/RedisCaffeineCache.java +++ b/src/main/java/com/pig4cloud/plugin/cache/support/RedisCaffeineCache.java @@ -5,8 +5,10 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.support.AbstractValueAdaptingCache; +import org.springframework.cache.support.NullValue; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -44,6 +46,10 @@ public class RedisCaffeineCache extends AbstractValueAdaptingCache { private final Map keyLockMap = new ConcurrentHashMap<>(); + private RedisSerializer stringSerializer = RedisSerializer.string(); + + private RedisSerializer javaSerializer = RedisSerializer.java(); + public RedisCaffeineCache(String name, RedisTemplate stringKeyRedisTemplate, Cache caffeineCache, CacheConfigProperties cacheConfigProperties) { super(cacheConfigProperties.isCacheNullValues()); @@ -105,11 +111,10 @@ public void put(Object key, Object value) { @Override public ValueWrapper putIfAbsent(Object key, Object value) { - Object cacheKey = getKey(key); Object prevValue; // 考虑使用分布式锁,或者将redis的setIfAbsent改为原子性操作 synchronized (key) { - prevValue = stringKeyRedisTemplate.opsForValue().get(cacheKey); + prevValue = getRedisValue(key); if (prevValue == null) { doPut(key, value); } @@ -118,14 +123,9 @@ public ValueWrapper putIfAbsent(Object key, Object value) { } private void doPut(Object key, Object value) { - Duration expire = getExpire(value); value = toStoreValue(value); - if (!expire.isNegative() && !expire.isZero()) { - stringKeyRedisTemplate.opsForValue().set(getKey(key), value, expire); - } - else { - stringKeyRedisTemplate.opsForValue().set(getKey(key), value); - } + Duration expire = getExpire(value); + setRedisValue(key, value, expire); push(new CacheMessage(this.name, key)); @@ -165,9 +165,7 @@ protected Object lookup(Object key) { return value; } - // 避免自动一个 RedisTemplate 覆盖失效 - stringKeyRedisTemplate.setKeySerializer(new StringRedisSerializer()); - value = stringKeyRedisTemplate.opsForValue().get(cacheKey); + value = getRedisValue(key); if (value != null) { log.debug("get cache from redis and put in caffeine, the key is : {}", cacheKey); @@ -186,7 +184,7 @@ private Duration getExpire(Object value) { if (cacheNameExpire == null) { cacheNameExpire = defaultExpiration; } - if (value == null && this.defaultNullValuesExpiration != null) { + if ((value == null || value == NullValue.INSTANCE) && this.defaultNullValuesExpiration != null) { cacheNameExpire = this.defaultNullValuesExpiration; } return cacheNameExpire; @@ -200,7 +198,19 @@ private Duration getExpire(Object value) { * @version 1.0.0 */ private void push(CacheMessage message) { - stringKeyRedisTemplate.convertAndSend(topic, message); + + /** + * 为了能自定义redisTemplate,发布订阅的序列化方式固定为jdk序列化方式。 + */ + Assert.hasText(topic, "a non-empty channel is required"); + byte[] rawChannel = stringSerializer.serialize(topic); + byte[] rawMessage = javaSerializer.serialize(message); + stringKeyRedisTemplate.execute((connection) -> { + connection.publish(rawChannel, rawMessage); + return null; + }, true); + + // stringKeyRedisTemplate.convertAndSend(topic, message); } /** @@ -220,4 +230,29 @@ public void clearLocal(Object key) { } } + private void setRedisValue(Object key, Object value, Duration expire) { + + Object convertValue = value; + if (value == null || value == NullValue.INSTANCE) { + convertValue = RedisNullValue.REDISNULLVALUE; + } + + if (!expire.isNegative() && !expire.isZero()) { + stringKeyRedisTemplate.opsForValue().set(getKey(key), convertValue, expire); + } + else { + stringKeyRedisTemplate.opsForValue().set(getKey(key), convertValue); + } + } + + private Object getRedisValue(Object key) { + + // NullValue在不同序列化方式中存在问题,因此自定义了RedisNullValue做个转化。 + Object value = stringKeyRedisTemplate.opsForValue().get(getKey(key)); + if (value != null && value instanceof RedisNullValue) { + value = NullValue.INSTANCE; + } + return value; + } + } diff --git a/src/main/java/com/pig4cloud/plugin/cache/support/RedisNullValue.java b/src/main/java/com/pig4cloud/plugin/cache/support/RedisNullValue.java new file mode 100644 index 0000000..a20de75 --- /dev/null +++ b/src/main/java/com/pig4cloud/plugin/cache/support/RedisNullValue.java @@ -0,0 +1,14 @@ +package com.pig4cloud.plugin.cache.support; + +import lombok.Data; + +import java.io.Serializable; + +@Data +class RedisNullValue implements Serializable { + + private static final long serialVersionUID = 1L; + + public static RedisNullValue REDISNULLVALUE = new RedisNullValue(); + +}