Skip to content

Commit e155222

Browse files
authored
Merge pull request #18 from FlyInWind1/master
取消写死的 redisSerializer,以及对 NullValue 的特殊处理
2 parents 94447b2 + d0261ba commit e155222

File tree

7 files changed

+163
-60
lines changed

7 files changed

+163
-60
lines changed

pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@
4646
<groupId>org.springframework.boot</groupId>
4747
<artifactId>spring-boot-starter-data-redis</artifactId>
4848
</dependency>
49+
<dependency>
50+
<groupId>org.springframework</groupId>
51+
<artifactId>spring-web</artifactId>
52+
<scope>provided</scope>
53+
</dependency>
54+
<dependency>
55+
<groupId>com.fasterxml.jackson.core</groupId>
56+
<artifactId>jackson-databind</artifactId>
57+
<scope>provided</scope>
58+
</dependency>
4959

5060
<dependency>
5161
<groupId>com.github.ben-manes.caffeine</groupId>

src/main/java/com/pig4cloud/plugin/cache/MultilevelCacheAutoConfiguration.java

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import com.pig4cloud.plugin.cache.properties.CacheConfigProperties;
44
import com.pig4cloud.plugin.cache.support.CacheMessageListener;
55
import com.pig4cloud.plugin.cache.support.RedisCaffeineCacheManager;
6+
import com.pig4cloud.plugin.cache.support.RedisCaffeineCaffeineCacheManagerCustomizer;
7+
import org.springframework.beans.factory.ObjectProvider;
68
import org.springframework.beans.factory.annotation.Qualifier;
79
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
810
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -15,8 +17,11 @@
1517
import org.springframework.data.redis.core.RedisTemplate;
1618
import org.springframework.data.redis.listener.ChannelTopic;
1719
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
20+
import org.springframework.data.redis.serializer.RedisSerializer;
1821
import org.springframework.data.redis.serializer.StringRedisSerializer;
1922

23+
import java.util.Objects;
24+
2025
/**
2126
* @author fuwei.deng
2227
* @version 1.0.0
@@ -27,10 +32,15 @@
2732
public class MultilevelCacheAutoConfiguration {
2833

2934
@Bean
35+
@ConditionalOnMissingBean
3036
@ConditionalOnBean(RedisTemplate.class)
3137
public RedisCaffeineCacheManager cacheManager(CacheConfigProperties cacheConfigProperties,
32-
@Qualifier("stringKeyRedisTemplate") RedisTemplate<Object, Object> stringKeyRedisTemplate) {
33-
return new RedisCaffeineCacheManager(cacheConfigProperties, stringKeyRedisTemplate);
38+
@Qualifier("stringKeyRedisTemplate") RedisTemplate<Object, Object> stringKeyRedisTemplate,
39+
ObjectProvider<RedisCaffeineCaffeineCacheManagerCustomizer> cacheManagerCustomizers) {
40+
RedisCaffeineCacheManager cacheManager = new RedisCaffeineCacheManager(cacheConfigProperties,
41+
stringKeyRedisTemplate);
42+
cacheManagerCustomizers.orderedStream().forEach(customizer -> customizer.customize(cacheManager));
43+
return cacheManager;
3444
}
3545

3646
/**
@@ -47,15 +57,26 @@ public RedisTemplate<Object, Object> stringKeyRedisTemplate(RedisConnectionFacto
4757
}
4858

4959
@Bean
60+
@ConditionalOnMissingBean(name = "cacheMessageListenerContainer")
5061
public RedisMessageListenerContainer cacheMessageListenerContainer(CacheConfigProperties cacheConfigProperties,
5162
@Qualifier("stringKeyRedisTemplate") RedisTemplate<Object, Object> stringKeyRedisTemplate,
52-
RedisCaffeineCacheManager redisCaffeineCacheManager) {
63+
@Qualifier("cacheMessageListener") CacheMessageListener cacheMessageListener) {
5364
RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
54-
redisMessageListenerContainer.setConnectionFactory(stringKeyRedisTemplate.getConnectionFactory());
55-
CacheMessageListener cacheMessageListener = new CacheMessageListener(redisCaffeineCacheManager);
65+
redisMessageListenerContainer
66+
.setConnectionFactory(Objects.requireNonNull(stringKeyRedisTemplate.getConnectionFactory()));
5667
redisMessageListenerContainer.addMessageListener(cacheMessageListener,
5768
new ChannelTopic(cacheConfigProperties.getRedis().getTopic()));
5869
return redisMessageListenerContainer;
5970
}
6071

72+
@Bean
73+
@SuppressWarnings("unchecked")
74+
@ConditionalOnMissingBean(name = "cacheMessageListener")
75+
public CacheMessageListener cacheMessageListener(
76+
@Qualifier("stringKeyRedisTemplate") RedisTemplate<Object, Object> stringKeyRedisTemplate,
77+
RedisCaffeineCacheManager redisCaffeineCacheManager) {
78+
return new CacheMessageListener((RedisSerializer<Object>) stringKeyRedisTemplate.getValueSerializer(),
79+
redisCaffeineCacheManager);
80+
}
81+
6182
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.pig4cloud.plugin.cache.config;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
6+
import com.fasterxml.jackson.core.JsonParser;
7+
import com.fasterxml.jackson.databind.DeserializationContext;
8+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
9+
import com.pig4cloud.plugin.cache.support.CacheMessage;
10+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
11+
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
12+
import org.springframework.cache.support.NullValue;
13+
import org.springframework.context.annotation.Configuration;
14+
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
15+
16+
/**
17+
* 为 jackson 添加序列化和反序列化 NullValue, CacheMessage 支持
18+
*
19+
* @author FlyInWind
20+
*/
21+
@Configuration(proxyBeanMethods = false)
22+
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
23+
public class CacheJackson2ObjectMapperBuilderCustomizer implements Jackson2ObjectMapperBuilderCustomizer {
24+
25+
@Override
26+
public void customize(Jackson2ObjectMapperBuilder builder) {
27+
// 由于 NullValue 有 final 修饰,而 jackson 配置的是 NON_FINAL
28+
// 会导致 @class 信息不会添加到 json 中,序列化出来的 json 为空,最终报错
29+
// 这里利用 ObjectMapper 的 mixIn 强制添加 @class 信息
30+
builder.mixIn(NullValue.class, UseTypeInfo.class);
31+
// 反序列化会创建新的对象,而由于 NullValue#equal 方法仅通过 == 判断是否相等,会导致 equal 结果为 false
32+
// 这里新建一个 Deserializer 专门返回 NullValue.INSTANCE
33+
builder.deserializers(NullValueDeserializer.INSTANCE);
34+
35+
// CacheMessage需要通过有参构造器构造
36+
builder.mixIn(CacheMessage.class, CacheMessageMix.class);
37+
}
38+
39+
public static class NullValueDeserializer extends StdDeserializer<NullValue> {
40+
41+
public static final NullValueDeserializer INSTANCE = new NullValueDeserializer();
42+
43+
protected NullValueDeserializer() {
44+
super(NullValue.class);
45+
}
46+
47+
@Override
48+
public NullValue deserialize(JsonParser p, DeserializationContext ctx) {
49+
return (NullValue) NullValue.INSTANCE;
50+
}
51+
52+
}
53+
54+
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
55+
public static class UseTypeInfo {
56+
57+
}
58+
59+
public static class CacheMessageMix {
60+
61+
@JsonCreator
62+
public CacheMessageMix(@JsonProperty("cacheName") String cacheName, @JsonProperty("key") Object key) {
63+
}
64+
65+
}
66+
67+
}

src/main/java/com/pig4cloud/plugin/cache/support/CacheMessageListener.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.pig4cloud.plugin.cache.support;
22

3+
import lombok.Getter;
34
import lombok.RequiredArgsConstructor;
45
import lombok.extern.slf4j.Slf4j;
56
import org.springframework.data.redis.connection.Message;
@@ -11,21 +12,18 @@
1112
* @version 1.0.0
1213
*/
1314
@Slf4j
15+
@Getter
1416
@RequiredArgsConstructor
1517
public class CacheMessageListener implements MessageListener {
1618

17-
private RedisSerializer<Object> javaSerializer = RedisSerializer.java();
19+
private final RedisSerializer<Object> redisSerializer;
1820

1921
private final RedisCaffeineCacheManager redisCaffeineCacheManager;
2022

2123
@Override
2224
public void onMessage(Message message, byte[] pattern) {
23-
24-
/**
25-
* 发送端固定了jdk序列户方式,接收端同样固定了jdk序列化方式进行反序列化。
26-
*/
27-
CacheMessage cacheMessage = (CacheMessage) javaSerializer.deserialize(message.getBody());
28-
log.debug("recevice a redis topic message, clear local cache, the cacheName is {}, the key is {}",
25+
CacheMessage cacheMessage = (CacheMessage) redisSerializer.deserialize(message.getBody());
26+
log.debug("receive a redis topic message, clear local cache, the cacheName is {}, the key is {}",
2927
cacheMessage.getCacheName(), cacheMessage.getKey());
3028
redisCaffeineCacheManager.clearLocal(cacheMessage.getCacheName(), cacheMessage.getKey());
3129
}

src/main/java/com/pig4cloud/plugin/cache/support/RedisCaffeineCache.java

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
import org.springframework.cache.support.AbstractValueAdaptingCache;
88
import org.springframework.cache.support.NullValue;
99
import org.springframework.data.redis.core.RedisTemplate;
10-
import org.springframework.data.redis.serializer.RedisSerializer;
11-
import org.springframework.util.Assert;
1210
import org.springframework.util.CollectionUtils;
1311
import org.springframework.util.StringUtils;
1412

@@ -24,12 +22,11 @@
2422
* @version 1.0.0
2523
*/
2624
@Slf4j
25+
@Getter
2726
public class RedisCaffeineCache extends AbstractValueAdaptingCache {
2827

29-
@Getter
3028
private final String name;
3129

32-
@Getter
3330
private final Cache<Object, Object> caffeineCache;
3431

3532
private final RedisTemplate<Object, Object> stringKeyRedisTemplate;
@@ -46,10 +43,6 @@ public class RedisCaffeineCache extends AbstractValueAdaptingCache {
4643

4744
private final Map<String, ReentrantLock> keyLockMap = new ConcurrentHashMap<>();
4845

49-
private RedisSerializer<String> stringSerializer = RedisSerializer.string();
50-
51-
private RedisSerializer<Object> javaSerializer = RedisSerializer.java();
52-
5346
public RedisCaffeineCache(String name, RedisTemplate<Object, Object> stringKeyRedisTemplate,
5447
Cache<Object, Object> caffeineCache, CacheConfigProperties cacheConfigProperties) {
5548
super(cacheConfigProperties.isCacheNullValues());
@@ -129,7 +122,7 @@ private void doPut(Object key, Object value) {
129122

130123
push(new CacheMessage(this.name, key));
131124

132-
caffeineCache.put(key, value);
125+
setCaffeineValue(key, value);
133126
}
134127

135128
@Override
@@ -159,7 +152,7 @@ public void clear() {
159152
@Override
160153
protected Object lookup(Object key) {
161154
Object cacheKey = getKey(key);
162-
Object value = caffeineCache.getIfPresent(key);
155+
Object value = getCaffeineValue(key);
163156
if (value != null) {
164157
log.debug("get cache from caffeine, the key is : {}", cacheKey);
165158
return value;
@@ -169,17 +162,17 @@ protected Object lookup(Object key) {
169162

170163
if (value != null) {
171164
log.debug("get cache from redis and put in caffeine, the key is : {}", cacheKey);
172-
caffeineCache.put(key, value);
165+
setCaffeineValue(key, value);
173166
}
174167
return value;
175168
}
176169

177-
private Object getKey(Object key) {
170+
protected Object getKey(Object key) {
178171
return this.name.concat(":").concat(
179-
StringUtils.isEmpty(cachePrefix) ? key.toString() : cachePrefix.concat(":").concat(key.toString()));
172+
StringUtils.hasLength(cachePrefix) ? cachePrefix.concat(":").concat(key.toString()) : key.toString());
180173
}
181174

182-
private Duration getExpire(Object value) {
175+
protected Duration getExpire(Object value) {
183176
Duration cacheNameExpire = expires.get(this.name);
184177
if (cacheNameExpire == null) {
185178
cacheNameExpire = defaultExpiration;
@@ -197,20 +190,8 @@ private Duration getExpire(Object value) {
197190
* @date 2018年1月31日 下午3:20:28
198191
* @version 1.0.0
199192
*/
200-
private void push(CacheMessage message) {
201-
202-
/**
203-
* 为了能自定义redisTemplate,发布订阅的序列化方式固定为jdk序列化方式。
204-
*/
205-
Assert.hasText(topic, "a non-empty channel is required");
206-
byte[] rawChannel = stringSerializer.serialize(topic);
207-
byte[] rawMessage = javaSerializer.serialize(message);
208-
stringKeyRedisTemplate.execute((connection) -> {
209-
connection.publish(rawChannel, rawMessage);
210-
return null;
211-
}, true);
212-
213-
// stringKeyRedisTemplate.convertAndSend(topic, message);
193+
protected void push(CacheMessage message) {
194+
stringKeyRedisTemplate.convertAndSend(topic, message);
214195
}
215196

216197
/**
@@ -230,29 +211,25 @@ public void clearLocal(Object key) {
230211
}
231212
}
232213

233-
private void setRedisValue(Object key, Object value, Duration expire) {
234-
235-
Object convertValue = value;
236-
if (value == null || value == NullValue.INSTANCE) {
237-
convertValue = RedisNullValue.REDISNULLVALUE;
238-
}
239-
214+
protected void setRedisValue(Object key, Object value, Duration expire) {
240215
if (!expire.isNegative() && !expire.isZero()) {
241-
stringKeyRedisTemplate.opsForValue().set(getKey(key), convertValue, expire);
216+
stringKeyRedisTemplate.opsForValue().set(getKey(key), value, expire);
242217
}
243218
else {
244-
stringKeyRedisTemplate.opsForValue().set(getKey(key), convertValue);
219+
stringKeyRedisTemplate.opsForValue().set(getKey(key), value);
245220
}
246221
}
247222

248-
private Object getRedisValue(Object key) {
223+
protected Object getRedisValue(Object key) {
224+
return stringKeyRedisTemplate.opsForValue().get(getKey(key));
225+
}
249226

250-
// NullValue在不同序列化方式中存在问题,因此自定义了RedisNullValue做个转化。
251-
Object value = stringKeyRedisTemplate.opsForValue().get(getKey(key));
252-
if (value != null && value instanceof RedisNullValue) {
253-
value = NullValue.INSTANCE;
254-
}
255-
return value;
227+
protected void setCaffeineValue(Object key, Object value) {
228+
caffeineCache.put(key, value);
229+
}
230+
231+
protected Object getCaffeineValue(Object key) {
232+
return caffeineCache.getIfPresent(key);
256233
}
257234

258235
}

src/main/java/com/pig4cloud/plugin/cache/support/RedisCaffeineCacheManager.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.github.benmanes.caffeine.cache.Caffeine;
44
import com.pig4cloud.plugin.cache.properties.CacheConfigProperties;
5+
import lombok.Getter;
6+
import lombok.Setter;
57
import lombok.extern.slf4j.Slf4j;
68
import org.springframework.cache.Cache;
79
import org.springframework.cache.CacheManager;
@@ -19,9 +21,11 @@
1921
* @version 1.0.0
2022
*/
2123
@Slf4j
24+
@Getter
25+
@Setter
2226
public class RedisCaffeineCacheManager implements CacheManager {
2327

24-
private ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>();
28+
private ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>();
2529

2630
private CacheConfigProperties cacheConfigProperties;
2731

@@ -47,16 +51,24 @@ public Cache getCache(String name) {
4751
return cache;
4852
}
4953
if (!dynamic && !cacheNames.contains(name)) {
50-
return cache;
54+
return null;
5155
}
5256

53-
cache = new RedisCaffeineCache(name, stringKeyRedisTemplate, caffeineCache(), cacheConfigProperties);
57+
cache = createCache(name);
5458
Cache oldCache = cacheMap.putIfAbsent(name, cache);
5559
log.debug("create cache instance, the cache name is : {}", name);
5660
return oldCache == null ? cache : oldCache;
5761
}
5862

63+
public RedisCaffeineCache createCache(String name) {
64+
return new RedisCaffeineCache(name, stringKeyRedisTemplate, caffeineCache(), cacheConfigProperties);
65+
}
66+
5967
public com.github.benmanes.caffeine.cache.Cache<Object, Object> caffeineCache() {
68+
return caffeineCacheBuilder().build();
69+
}
70+
71+
public Caffeine<Object, Object> caffeineCacheBuilder() {
6072
Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();
6173
doIfPresent(cacheConfigProperties.getCaffeine().getExpireAfterAccess(), cacheBuilder::expireAfterAccess);
6274
doIfPresent(cacheConfigProperties.getCaffeine().getExpireAfterWrite(), cacheBuilder::expireAfterWrite);
@@ -84,10 +96,11 @@ public com.github.benmanes.caffeine.cache.Cache<Object, Object> caffeineCache()
8496
break;
8597
case SOFT:
8698
cacheBuilder.softValues();
99+
break;
87100
default:
88101
}
89102
}
90-
return cacheBuilder.build();
103+
return cacheBuilder;
91104
}
92105

93106
protected static void doIfPresent(Duration duration, Consumer<Duration> consumer) {

0 commit comments

Comments
 (0)