使用spring cache+Redis整合redis hash等数据结构

Spring cache源码分析

preview

  • Cache、CacheManager:Cache抽象了缓存的通用操作,如get、put,而CacheManager是Cache的集合,之所以需要多个Cache对象,是因为需要多种缓存失效时间、缓存条目上限等
  • CacheInterceptor、CacheAspectSupport、AbstractCacheInvoker:CacheInterceptor是一个AOP方法拦截器,在方法前后做额外的逻辑,也即查询缓存、写入缓存等,它继承了CacheAspectSupport(缓存操作的主体逻辑)、AbstractCacheInvoker(封装了对Cache的读写)
  • CacheOperation、AnnotationCacheOperationSource、SpringCacheAnnotationParser:CacheOperation定义了缓存操作的缓存名字、缓存key、缓存条件condition、CacheManager等,AnnotationCacheOperationSource是一个获取缓存注解对应CacheOperation的类,而SpringCacheAnnotationParser是真正解析注解的类,解析后会封装成CacheOperation集合供AnnotationCacheOperationSource查找

源码分析

1、解析注解

SpringCacheAnnotationParser:负责解析注解,返回CacheOperation集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {

// 解析类级别的缓存注解
@Override
public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {
DefaultCacheConfig defaultConfig = getDefaultCacheConfig(type);
return parseCacheAnnotations(defaultConfig, type);
}

// 解析方法级别的缓存注解
@Override
public Collection<CacheOperation> parseCacheAnnotations(Method method) {
DefaultCacheConfig defaultConfig = getDefaultCacheConfig(method.getDeclaringClass());
return parseCacheAnnotations(defaultConfig, method);
}

// 解析缓存注解
private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
Collection<CacheOperation> ops = null;

// 解析@Cacheable注解
Collection<Cacheable> cacheables = AnnotatedElementUtils.getAllMergedAnnotations(ae, Cacheable.class);
if (!cacheables.isEmpty()) {
ops = lazyInit(ops);
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
}
}

// 解析@CacheEvict注解
Collection<CacheEvict> evicts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CacheEvict.class);
if (!evicts.isEmpty()) {
ops = lazyInit(ops);
for (CacheEvict evict : evicts) {
ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
}
}

// 解析@CachePut注解
Collection<CachePut> puts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CachePut.class);
if (!puts.isEmpty()) {
ops = lazyInit(ops);
for (CachePut put : puts) {
ops.add(parsePutAnnotation(ae, cachingConfig, put));
}
}

// 解析@Caching注解
Collection<Caching> cachings = AnnotatedElementUtils.getAllMergedAnnotations(ae, Caching.class);
if (!cachings.isEmpty()) {
ops = lazyInit(ops);
for (Caching caching : cachings) {
Collection<CacheOperation> cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
if (cachingOps != null) {
ops.addAll(cachingOps);
}
}
}

return ops;
}

AnnotationCacheOperationSource:调用SpringCacheAnnotationParser获取注解对应CacheOperation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable {

// 查找类级别的CacheOperation列表
@Override
protected Collection<CacheOperation> findCacheOperations(final Class<?> clazz) {
return determineCacheOperations(new CacheOperationProvider() {
@Override
public Collection<CacheOperation> getCacheOperations(CacheAnnotationParser parser) {
return parser.parseCacheAnnotations(clazz);
}
});

}

// 查找方法级别的CacheOperation列表
@Override
protected Collection<CacheOperation> findCacheOperations(final Method method) {
return determineCacheOperations(new CacheOperationProvider() {
@Override
public Collection<CacheOperation> getCacheOperations(CacheAnnotationParser parser) {
return parser.parseCacheAnnotations(method);
}
});
}

}

AbstractFallbackCacheOperationSource:AnnotationCacheOperationSource的父类,实现了获取CacheOperation的通用逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public abstract class AbstractFallbackCacheOperationSource implements CacheOperationSource {

/**
* Cache of CacheOperations, keyed by method on a specific target class.
* <p>As this base class is not marked Serializable, the cache will be recreated
* after serialization - provided that the concrete subclass is Serializable.
*/
private final Map<Object, Collection<CacheOperation>> attributeCache =
new ConcurrentHashMap<Object, Collection<CacheOperation>>(1024);


// 根据Method、Class反射信息,获取对应的CacheOperation列表
@Override
public Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass) {
if (method.getDeclaringClass() == Object.class) {
return null;
}

Object cacheKey = getCacheKey(method, targetClass);
Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);

// 因解析反射信息较耗时,所以用map缓存,避免重复计算
// 如在map里已记录,直接返回
if (cached != null) {
return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);
}
// 否则做一次计算,然后写入map
else {
Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
if (cacheOps != null) {
if (logger.isDebugEnabled()) {
logger.debug("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
}
this.attributeCache.put(cacheKey, cacheOps);
}
else {
this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
}
return cacheOps;
}
}

// 计算缓存操作列表,优先用target代理类的方法上的注解,如果不存在则其次用target代理类,再次用原始类的方法,最后用原始类
private Collection<CacheOperation> computeCacheOperations(Method method, Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}

// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
// If we are dealing with method with generic parameters, find the original method.
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);

// 调用findCacheOperations(由子类AnnotationCacheOperationSource实现),最终通过SpringCacheAnnotationParser来解析
// First try is the method in the target class.
Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
if (opDef != null) {
return opDef;
}

// Second try is the caching operation on the target class.
opDef = findCacheOperations(specificMethod.getDeclaringClass());
if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}

if (specificMethod != method) {
// Fallback is to look at the original method.
opDef = findCacheOperations(method);
if (opDef != null) {
return opDef;
}
// Last fallback is the class of the original method.
opDef = findCacheOperations(method.getDeclaringClass());
if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}
}

return null;
}

2、逻辑执行

以@Cacheable背后的逻辑为例。预期是先查缓存,如果缓存命中了就直接使用缓存值,否则执行业务逻辑,并把结果写入缓存。

ProxyCachingConfiguration:是一个配置类,用于生成CacheInterceptor类和CacheOperationSource类的Spring bean

CacheInterceptor:是一个AOP方法拦截器,它通过CacheOperationSource获取第1步解析注解的CacheOperation结果(如缓存名字、缓存key、condition条件),本质上是拦截原始方法的执行,在之前、之后增加逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 核心类,缓存拦截器
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {

// 拦截原始方法的执行,在之前、之后增加逻辑
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();

// 封装原始方法的执行到一个回调接口,便于后续调用
CacheOperationInvoker aopAllianceInvoker = new CacheOperationInvoker() {
@Override
public Object invoke() {
try {
// 原始方法的执行
return invocation.proceed();
}
catch (Throwable ex) {
throw new ThrowableWrapper(ex);
}
}
};

try {
// 调用父类CacheAspectSupport的方法
return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}

}

CacheAspectSupport:缓存切面支持类,是CacheInterceptor的父类,封装了所有的缓存操作的主体逻辑

主要流程如下:

  1. 通过CacheOperationSource,获取所有的CacheOperation列表
  2. 如果有@CacheEvict注解、并且标记为在调用前执行,则做删除/清空缓存的操作
  3. 如果有@Cacheable注解,查询缓存
  4. 如果缓存未命中(查询结果为null),则新增到cachePutRequests,后续执行原始方法后会写入缓存
  5. 缓存命中时,使用缓存值作为结果;缓存未命中、或有@CachePut注解时,需要调用原始方法,使用原始方法的返回值作为结果
  6. 如果有@CachePut注解,则新增到cachePutRequests
  7. 如果缓存未命中,则把查询结果值写入缓存;如果有@CachePut注解,也把方法执行结果写入缓存
  8. 如果有@CacheEvict注解、并且标记为在调用后执行,则做删除/清空缓存的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// 核心类,缓存切面支持类,封装了所有的缓存操作的主体逻辑
public abstract class CacheAspectSupport extends AbstractCacheInvoker
implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {

// CacheInterceptor调父类的该方法
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
if (this.initialized) {
Class<?> targetClass = getTargetClass(target);
// 通过CacheOperationSource,获取所有的CacheOperation列表
Collection<CacheOperation> operations = getCacheOperationSource().getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
// 继续调一个private的execute方法执行
return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass));
}
}

// 如果spring bean未初始化完成,则直接调用原始方法。相当于原始方法没有缓存功能。
return invoker.invoke();
}

private的execute方法
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// Special handling of synchronized invocation
if (contexts.isSynchronized()) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = context.getCaches().iterator().next();
try {
return wrapCacheValue(method, cache.get(key, new Callable<Object>() {
@Override
public Object call() throws Exception {
return unwrapReturnValue(invokeOperation(invoker));
}
}));
}
catch (Cache.ValueRetrievalException ex) {
// The invoker wraps any Throwable in a ThrowableWrapper instance so we
// can just make sure that one bubbles up the stack.
throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();
}
}
else {
// No caching required, only call the underlying method
return invokeOperation(invoker);
}
}

// 如果有@CacheEvict注解、并且标记为在调用前执行,则做删除/清空缓存的操作
// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);

// 如果有@Cacheable注解,查询缓存
// Check if we have a cached item matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

// 如果缓存未命中(查询结果为null),则新增到cachePutRequests,后续执行原始方法后会写入缓存
// Collect puts from any @Cacheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}

Object cacheValue;
Object returnValue;

if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
// 缓存命中的情况,使用缓存值作为结果
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// 缓存未命中、或有@CachePut注解的情况,需要调用原始方法
// Invoke the method if we don't have a cache hit
// 调用原始方法,得到结果值
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}

// 如果有@CachePut注解,则新增到cachePutRequests
// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

// 如果缓存未命中,则把查询结果值写入缓存;如果有@CachePut注解,也把方法执行结果写入缓存
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}

// 如果有@CacheEvict注解、并且标记为在调用后执行,则做删除/清空缓存的操作
// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

return returnValue;
}

private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
// 如果满足condition条件,才查询缓存
if (isConditionPassing(context, result)) {
// 生成缓存key,如果注解中指定了key,则按照Spring表达式解析,否则使用KeyGenerator类生成
Object key = generateKey(context, result);
// 根据缓存key,查询缓存值
Cache.ValueWrapper cached = findInCaches(context, key);
if (cached != null) {
return cached;
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
}
}
}
}
return null;
}

private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
for (Cache cache : context.getCaches()) {
// 调用父类AbstractCacheInvoker的doGet方法,查询缓存
Cache.ValueWrapper wrapper = doGet(cache, key);
if (wrapper != null) {
if (logger.isTraceEnabled()) {
logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
}
return wrapper;
}
}
return null;
}

核心逻辑######

AbstractCacheInvoker:CacheAspectSupport的父类,封装了最终查询Cache接口的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class AbstractCacheInvoker {
// 最终查询缓存的方法
protected Cache.ValueWrapper doGet(Cache cache, Object key) {
try {
// 调用Spring Cache接口的查询方法
return cache.get(key);
}
catch (RuntimeException ex) {
getErrorHandler().handleCacheGetError(ex, cache, key);
return null; // If the exception is handled, return a cache miss
}
}
}

在项目中,某些业务场景需要使用到redis hash 或其他数据接口,而原生的spring cache其实是没有支持的,只能手动通过RedisTemplate,或者Redisson去编程式使用,如果我们想通过spring cache注解@Cacheable缓存hash数据结构,可以去自定义实现。

简单写了一个springboot starter:

https://gitee.com/gump12138/cache-spring-boot-starter.git

image-20220814161238494

spring chache + Redis整合

自定义cache

实现参考org.springframework.data.redis.cache.RedisCache:

  • 继承AbstractValueAdaptingCache
  • 重写spring顶层cache接口的几个方法:
1
2
3
4
5
protected Object lookup(Object key){};
public synchronized <T> T get(Object key, Callable<T> valueLoader){};
public void put(Object key, Object value){};
public void evict(Object key){};
...
  • 自定义序列化方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
/**
* @description: 自定义cache 用于缓存Redis hash数据
* @author: zhangzc
* @modified By: zhangzc
* @date: Created in 2022/7/13 11:07
* @version:v1.0
* @see RedisCache
*/
public class RedisHashCache extends AbstractValueAdaptingCache {

private static final byte[] BINARY_NULL_VALUE = RedisSerializer.java().serialize(NullValue.INSTANCE);

/**
* 缓存name
* <p>
* 现在只有: constraints
* </p>
*/
private final String name;

/**
* redis config
*/
private final RedisCacheConfiguration cacheConfig;
private final ConversionService conversionService;


public RedisHashCache(String name, RedisCacheConfiguration redisCacheConfiguration) {
super(true);

Assert.notNull(name, "Name must not be null");

this.name = name;
this.cacheConfig = redisCacheConfiguration;
this.conversionService = cacheConfig.getConversionService();

Assert.notNull(cacheConfig, "CacheConfig must not be null");
}

/**
* @param key the key whose associated value is to be returned
* @return Object
* @see AbstractValueAdaptingCache#get(Object)
*/
@SneakyThrows
@Override
protected Object lookup(Object key) {
HashKey hashKey = convertHashKey(key);

if (hashKey.getHKey() == null) {
Map<String, String> valueMap = RedisUtils.hGetall(hashKey.getRKey());
if (valueMap.isEmpty()) {
return null;
}
Map<Object, Object> convertedCacheValueMap = new HashMap<>();
for (Entry<String, String> entry : valueMap.entrySet()) {
// todo key只限制为了Long 待修改
convertedCacheValueMap.put(Long.valueOf(entry.getKey()), deserializeCacheValue(entry.getValue().getBytes()));
}

return convertedCacheValueMap;


}
Object value = RedisUtils.hGet(hashKey.getRKey(), hashKey.getHKey());
if (value == null) {
return null;
}

return deserializeCacheValue(((String) value).getBytes(CHAR_SET));
}

/**
* Serialize the value to cache.
*
* @param value must not be {@literal null}.
* @return never {@literal null}.
*/
@SneakyThrows
protected byte[] serializeCacheValue(Object value) {

if (isAllowNullValues() && value instanceof NullValue) {
return BINARY_NULL_VALUE;
}

return ByteUtils.getBytes(cacheConfig.getValueSerializationPair().write(value));
}


@Override
public String getName() {
return name;
}

@Override
public Object getNativeCache() {
throw new RuntimeException("暂时不支持");
}

@Override
public synchronized <T> T get(Object key, Callable<T> valueLoader) {
throw new RuntimeException("暂时不支持");
}

@Override
public void put(Object key, Object value) {
Object cacheValue = preProcessCacheValue(value);

if (!isAllowNullValues() && cacheValue == null) {
throw new IllegalArgumentException(String.format("Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' "
+ "or configure RedisHashCache to allow 'null' via RedisHashCacheConfiguration.", name));
}

if (cacheValue instanceof Map) {

Map valueMap = (Map) value;

Map<String, String> targetMap = new HashMap<>();
valueMap.forEach((k, v) -> targetMap.put(covertKeyToString(k), covertAndSerializeValueToString(v)));

RedisUtils.hPutAll(name + Constant.DOUBLE_COLON + convertKey(key), targetMap, cacheConfig.getTtl());

} else {
HashKey hashKey = convertHashKey(key);
RedisUtils.hPut(hashKey.getRKey(), hashKey.getHKey(), covertAndSerializeValueToString(cacheValue), cacheConfig.getTtl());
}


}

private String covertKeyToString(Object cacheRedisKey) {
String hKey;
try {
hKey = String.valueOf(cacheRedisKey);
} catch (Exception e) {
throw new RuntimeException(e);
}

return hKey;
}

private String covertAndSerializeValueToString(Object cacheValue) {
String hValue;
try {
hValue = new String(serializeCacheValue(cacheValue), CHAR_SET);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return hValue;
}

/**
* Convert {@code key} to a {@link String} representation used for cache key creation.
*
* @param key will never be {@literal null}.
* @return never {@literal null}.
* @throws IllegalStateException if {@code key} cannot be converted to {@link String}.
*/
protected String convertKey(Object key) {

if (key instanceof String) {
return (String) key;
}

TypeDescriptor source = TypeDescriptor.forObject(key);

if (conversionService.canConvert(source, TypeDescriptor.valueOf(String.class))) {
try {
return conversionService.convert(key, String.class);
} catch (ConversionFailedException e) {

// may fail if the given key is a collection
if (isCollectionLikeOrMap(source)) {
return convertCollectionLikeOrMapKey(key, source);
}

throw e;
}
}

Method toString = ReflectionUtils.findMethod(key.getClass(), "toString");

if (toString != null && !Object.class.equals(toString.getDeclaringClass())) {
return key.toString();
}

throw new IllegalStateException(String.format(
"Cannot convert cache key %s to String. Please register a suitable Converter via 'RedisCacheConfiguration.configureKeyConverters(...)' or override '%s.toString()'.",
source, key.getClass().getSimpleName()));
}

private String convertCollectionLikeOrMapKey(Object key, TypeDescriptor source) {

if (source.isMap()) {

StringBuilder target = new StringBuilder("{");

for (Entry<?, ?> entry : ((Map<?, ?>) key).entrySet()) {
target.append(convertKey(entry.getKey())).append("=").append(convertKey(entry.getValue()));
}
target.append("}");

return target.toString();
} else if (source.isCollection() || source.isArray()) {

StringJoiner sj = new StringJoiner(",");

Collection<?> collection = source.isCollection() ? (Collection<?>) key : Arrays.asList(ObjectUtils.toObjectArray(key));

for (Object val : collection) {
sj.add(convertKey(val));
}
return "[" + sj.toString() + "]";
}

throw new IllegalArgumentException(String.format("Cannot convert cache key %s to String.", key));
}

private boolean isCollectionLikeOrMap(TypeDescriptor source) {
return source.isArray() || source.isCollection() || source.isMap();
}

private String prefixCacheKey(String key) {

// allow contextual cache names by computing the key prefix on every call.
return cacheConfig.getKeyPrefixFor(name) + key;
}

/**
* Customization hook called before passing object to
* {@link org.springframework.data.redis.serializer.RedisSerializer}.
*
* @param value can be {@literal null}.
* @return preprocessed value. Can be {@literal null}.
*/
@Nullable
protected Object preProcessCacheValue(@Nullable Object value) {

if (value != null) {
return value;
}

return isAllowNullValues() ? NullValue.INSTANCE : null;
}

@Override
public ValueWrapper putIfAbsent(Object key, Object value) {
throw new RuntimeException("暂时不实现");
}

@Override
public void evict(Object key) {
HashKey hashKey = convertHashKey(key);

RedisUtils.hDel(hashKey.getRKey(), hashKey.getHKey());
}

/**
* 暂定义 key= RedisKey:HashKey
*
* @param key key
* @return HashKey
*/
protected HashKey convertHashKey(Object key) {
if (!(key instanceof String)) {
throw new IllegalStateException(String.format("Cannot convert %s to String. Register a Converter or override toString().", key));
}
String[] keyArray = Optional.of((String) key).map(k -> k.split(Constant.COLON)).orElse(new String[0]);

if (keyArray.length == 1) {
return new HashKey(name + Constant.DOUBLE_COLON + keyArray[0]);
}

if (keyArray.length < 2) {
throw new IllegalStateException(String.format("Cannot convert %s to String. Register a Converter or override toString().", key));
}

return new HashKey(name + Constant.DOUBLE_COLON + keyArray[0], keyArray[1]);
}

@Override
public void clear() {

}

@Getter
@AllArgsConstructor
public static class HashKey {

private String RKey;
private String HKey;

public HashKey(String RKey) {
this.RKey = RKey;
}
}

/**
* Deserialize the given value to the actual cache value.
*
* @param value must not be {@literal null}.
* @return can be {@literal null}.
*/
@Nullable
protected Object deserializeCacheValue(byte[] value) {

if (isAllowNullValues() && ObjectUtils.nullSafeEquals(value, BINARY_NULL_VALUE)) {
return NullValue.INSTANCE;
}

return cacheConfig.getValueSerializationPair().read(ByteBuffer.wrap(value));
}

}

自定义cacheManager

主要是为了管理我们指定的cache(支持缓存hash等)

1
private Collection<? extends RedisHashCache> caches;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @description: 用于Redis存取hash cache
* @author: zhangzc
* @modified By: zhangzc
* @date: Created in 2022/7/13 11:52
* @version:v1.0
* @see RedisCacheManager
*/
public class RedisHashCacheManager extends AbstractCacheManager {

public static final String BEAN_NAME = "redisHashCacheManager";

private Collection<? extends RedisHashCache> caches;

public void setCaches(Collection<? extends RedisHashCache> caches) {
this.caches = caches;
}

public Collection<? extends RedisHashCache> getCaches() {
return caches;
}

@Override
protected Collection<? extends Cache> loadCaches() {
return this.caches;
}
}

配置类

1
2
3
4
5
6
7
8
9
10
@Bean(RedisHashCacheManager.BEAN_NAME)  
#指定我们自定义的缓存管理器 在使用的时候需要指定
#
@Override
@Cacheable(cacheManager = RedisHashCacheManager.BEAN_NAME, value = CacheConstant.CONSTRAINTS_TEMPLATE_PREFIX,
key = CacheConstant.CONSTRAINTS_TEMPLATE_ID_KEY, unless = "#result eq null || #result.size() < 1 ")
public Map<Long, List<DataTypeConstraintValueBO>> getConstraintsMapByTemplateOnlyCached(Long templateId) {
...
return Collections.emptyMap();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package cache.config;


import cache.domain.Constant;
import cache.rediscache.RedisHashCache;
import cache.rediscache.RedisHashCacheManager;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;


/**
* @description: 配置RedisHashCacheManage
* @author: zhangzc
* @modified By: zhangzc
* @date: Created in 2022/7/13 12:02
* @version:v1.0
*/
@Configuration
@ConditionalOnProperty("cache.enable")
@EnableConfigurationProperties(RedisManagerProperties.class)
public class RedisHashCacheConfiguration {

@Autowired
private RedisManagerProperties redisManagerProperties;

public RedisHashCacheConfiguration(DefaultListableBeanFactory factory) {
factory.getBeanDefinition(RedisHashCacheManager.BEAN_NAME).setAutowireCandidate(false);
}

/**
* 自定义hash cacheManage
* <p>
* 使用此manager
* </p>
*
* @return CacheManager
*/
@Bean(RedisHashCacheManager.BEAN_NAME)
public CacheManager redisHashCacheManager(GenericToStringSerializer<String> genericToStringSerializer,
GenericJackson2JsonRedisSerializer genericJsonRedisSerializer) {

RedisHashCacheManager cacheManager = new RedisHashCacheManager();
List<RedisHashCache> caches = new ArrayList<>(1);

RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(redisManagerProperties.getTtlDuration()))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(genericToStringSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericJsonRedisSerializer));

/**
* @see RedisCacheManager#getMissingCache(String)
*/
// 手动添加缓存名 如果要使用这个cacheManger 每次加一个cacheName都需要在这里加上 todo 可以优化
caches.add(new RedisHashCache(Constant.CONSTRAINTS_TEMPLATE_PREFIX, redisCacheConfiguration));
cacheManager.setCaches(caches);

return cacheManager;
}

/**
* 指定 @Cacheable默认的manager
*/
@Bean
@Primary
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, GenericToStringSerializer<String> genericToStringSerializer,
GenericJackson2JsonRedisSerializer genericJsonRedisSerializer) {

RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);

RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(redisManagerProperties.getTtlDuration()))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(genericToStringSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericJsonRedisSerializer));

return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}

@Bean
public GenericToStringSerializer<String> genericToStringSerializer() {
return new GenericToStringSerializer<>(String.class);
}

@Bean
public GenericJackson2JsonRedisSerializer genericJsonRedisSerializer() {
ObjectMapper redisJsonMapper = Jackson2ObjectMapperBuilder.json().serializationInclusion(JsonInclude.Include.NON_NULL)
.failOnUnknownProperties(false).featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build();
// 把类名作为属性进行存储
redisJsonMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
// 剔除类上的所有注解
redisJsonMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
redisJsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 枚举类使用toString方法进行反序列化
redisJsonMapper.configure(DeserializationFeature.READ_ENUMS_USING_TO_STRING, true);
// 枚举类使用toString方法进行反序列化
redisJsonMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
return new GenericJackson2JsonRedisSerializer(redisJsonMapper);
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
@ConfigurationProperties(prefix = "cache-hash")
public class RedisManagerProperties {

/**
* 实体缓存有效期,单位 秒,默认7200秒,即2小时
*/
private int ttlDuration = 7200;

public int getTtlDuration() {
return ttlDuration;
}

public void setTtlDuration(int ttlDuration) {
this.ttlDuration = ttlDuration;
}
}

Redis工具类

整合RedisTemplate、Redisson

Redisson可以保证设置缓存同时设置失效时间的原子性 基于lua脚本打包命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
package cache.util;


import static cache.domain.Constant.CHAR_SET;

import cache.helper.SpringContextHelper;
import java.time.Duration;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.CollectionUtils;

/**
* @description: Redis Utils
* @author: zhangzc
* @modified By: zhangzc
* @date: Created in 2022/7/8 11:50
* @version:v1.0
*/
@Slf4j
public class RedisUtils {

public RedisUtils() {
}

private static final StringRedisTemplate redisTemplate = SpringContextHelper
.getBean("stringRedisTemplate", StringRedisTemplate.class);

private static final RedissonClient redissonClient = SpringContextHelper
.getBean("redisson", Redisson.class);

/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public static boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}

/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public static boolean expire(final String key, final long timeout, final TimeUnit unit) {
Boolean ret = redisTemplate.expire(key, timeout, unit);
return ret != null && ret;
}

/**
* 删除单个key
*
* @param key 键
* @return true=删除成功;false=删除失败
*/
public static boolean del(final String key) {
Boolean ret = redisTemplate.delete(key);
return ret != null && ret;
}

/**
* 删除多个key
*
* @param keys 键集合
* @return 成功删除的个数
*/
public static long del(final Collection<String> keys) {
Long ret = redisTemplate.delete(keys);
return ret == null ? 0 : ret;
}

/**
* 存入普通对象
*
* @param key Redis键
* @param value 值
*/
public static void set(final String key, final String value) {
redisTemplate.opsForValue().set(key, value, 1, TimeUnit.MINUTES);
}

/**
* 存入普通对象
*
* @param key 键
* @param value 值
* @param timeout 有效期,单位秒
*/
public static void set(final String key, final String value, final long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}

/**
* 获取普通对象
*
* @param key 键
* @return 对象
*/
public static Object get(final String key) {
return redisTemplate.opsForValue().get(key);
}

// -------------- hash操作start ------------

/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public static void hPut(final String key, final String hKey, final Object value) {
redisTemplate.opsForHash().put(key, hKey, value);
}

/**
* 往Hash中存入数据 并设置过期时间
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
* @param duration ttl
*/
public static void hPut(final String key, final String hKey, final String value, Duration duration) {
RMap<String, String> map = redissonClient.getMap(key, new StringCodec(CHAR_SET));
map.put(hKey, value);
map.expire(duration.getSeconds(), TimeUnit.SECONDS);
}

/**
* 往hash存入整个hash
*
* @param key RedisKey
* @param values valueMap
* @param duration ttl
*/
public static void hPutAll(final String key, final Map<String, String> values, Duration duration) {
RMap<String, String> map = redissonClient.getMap(key, new StringCodec(CHAR_SET));
map.putAll(values);
map.expire(duration.getSeconds(), TimeUnit.SECONDS);
}

/**
* 往Hash中存入多个数据
*
* @param key Redis键
* @param values Hash键值对
*/
public static void hPutAll(final String key, final Map<String, Object> values) {
redisTemplate.opsForHash().putAll(key, values);
}

/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public static Object hGet(final String key, final String hKey) {
return redisTemplate.opsForHash().get(key, hKey);
}

/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public static List<Object> hMultiGet(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}

/**
* 获取hash中所有的数据
*
* @param key Redis键
* @return HashMap
*/
public static Map<String, String> hGetall(String key) {
return redisTemplate.execute((RedisCallback<Map<String, String>>) con -> {
Map<byte[], byte[]> result = con.hGetAll(key.getBytes());
if (CollectionUtils.isEmpty(result)) {
return new HashMap<>(0);
}

Map<String, String> ans = new HashMap<>(result.size());
for (Map.Entry<byte[], byte[]> entry : result.entrySet()) {
ans.put(new String(entry.getKey()), new String(entry.getValue()));
}
return ans;
});
}

/**
* 指定hash中hKey删除 可多key
*
* @param key Redis键
* @param hKey Hash键
*/
public static void hDel(final String key, final String hKey) {
if (hKey == null) {
del(key);
if (log.isDebugEnabled()) {
log.info("删除整个hash,RKey={}", key);
}
return;
}
redisTemplate.opsForHash().delete(key, hKey);
}

/**
* 指定RedisKey删除 整个hash
*
* @param key RedisKey
*/
public static void hDelAll(final String key) {
redisTemplate.opsForHash().delete(key);
}
// -------------- hash操作end ------------

// public Map<String, String> hmget(String key, List<String> fields) {
// List<String> result = redisTemplate.<String, String>opsForHash().multiGet(key, fields);
// Map<String, String> ans = new HashMap<>(fields.size());
// int index = -1;
// for (String field : fields) {
// ++index;
// if (result.get(index) == null) {
// continue;
// }
// ans.put(field, result.get(index));
// }
// return ans;
// }

/**
* 往Set中存入数据
*
* @param key Redis键
* @param values 值
* @return 存入的个数
*/
public static long sSet(final String key, final String... values) {
Long count = redisTemplate.opsForSet().add(key, values);
return count == null ? 0 : count;
}

/**
* 删除Set中的数据
*
* @param key Redis键
* @param values 值
* @return 移除的个数
*/
public static long sDel(final String key, final Object... values) {
Long count = redisTemplate.opsForSet().remove(key, values);
return count == null ? 0 : count;
}

/**
* 往List中存入数据
*
* @param key Redis键
* @param value 数据
* @return 存入的个数
*/
public static long lPush(final String key, final String value) {
Long count = redisTemplate.opsForList().rightPush(key, value);
return count == null ? 0 : count;
}

/**
* 往List中存入多个数据
*
* @param key Redis键
* @param values 多个数据
* @return 存入的个数
*/
public static long lPushAll(final String key, final Collection<String> values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
return count == null ? 0 : count;
}

/**
* 往List中存入多个数据
*
* @param key Redis键
* @param values 多个数据
* @return 存入的个数
*/
public static long lPushAll(final String key, final String... values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
return count == null ? 0 : count;
}

/**
* 从List中获取begin到end之间的元素
*
* @param key Redis键
* @param start 开始位置
* @param end 结束位置(start=0,end=-1表示获取全部元素)
* @return List对象
*/
public static List<String> lGet(final String key, final int start, final int end) {
return redisTemplate.opsForList().range(key, start, end);
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author:zzc
* @date: 2022/7/27
*/
public class Constant {

public final static String CHAR_SET = "UTF-8";

public static final String DOUBLE_COLON = "::";

public static final String COLON = ":";

public static final String ASTERISK = "*";

public static final String CONSTRAINTS_TEMPLATE_PREFIX = "constraints::templateId";
}

开启使用

1
2
3
4
5
6
7
8
9
@SpringBootApplication(exclude = RedissonAutoConfiguration.class)
@EnableCaching
public class CacheApplication {

public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}

}