一些Maven
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.31</version>
</dependency>
<!-- Spring Cache 在这里面包含了 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.10</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
这里面自定义cacheManager,配置序列化方式、自动获取并设置ttl
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.cache.*;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.lang.Nullable;
import java.time.Duration;
import java.util.Map;
/**
* @author fly_roushan
* 2023/5/11 16:25
*/
public class CustomizerRedisCacheManager extends RedisCacheManager {
// 自定义ttl的分隔符
private static final String DEFAULT_SEPARATOR = "#";
// 用来保存返回值信息和注解,方便获取所需信息及自定义value的序列化
private final Map<String, CacheInfo> typeMap;
// 默认的key的序列化方式
private static final RedisSerializationContext.SerializationPair<String> DEFAULT_KEY_SERIALIZATION = RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
public CustomizerRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, CacheInfo> typeMap) {
super(cacheWriter, defaultCacheConfiguration);
this.typeMap = typeMap;
}
@Override
protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
if (cacheConfig == null) {
return super.createRedisCache(name, null);
}
// 获取并设置ttl
Duration ttl = getTtlByName(name);
if (ttl != null) {
cacheConfig = cacheConfig.entryTtl(ttl);
}
// 设置key前缀,没有key时不使用:
// 默认生成的key时这样, cacheName::key,很难看,而且会出现空目录
// 现在有key就是, cacheName:key
// 没有key就是, cacheName, 不会有额外的冒号
CacheInfo info = typeMap.get(name);
if (StringUtils.isNotBlank(info.getCacheable().key())) {
cacheConfig = cacheConfig.computePrefixWith(DEFAULT_CACHE_KEY_PREFIX);
} else {
cacheConfig = cacheConfig.computePrefixWith(cacheName -> cacheName);
}
cacheConfig = cacheConfig
.serializeKeysWith(DEFAULT_KEY_SERIALIZATION)
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new FastJsonRedisSerializer<>(info.getReturnType())));
// 去除最终生成的key中用来设置ttl的字符
return super.createRedisCache(name.substring(0, name.indexOf(DEFAULT_SEPARATOR)), cacheConfig);
}
private static final CacheKeyPrefix DEFAULT_CACHE_KEY_PREFIX = cacheName -> cacheName + ":";
private Duration getTtlByName(String name) {
if (name == null) {
return null;
}
String[] cacheParams = name.split(DEFAULT_SEPARATOR);
if (cacheParams.length > 1) {
String seconds = cacheParams[1];
if (StringUtils.isNotBlank(seconds)) {
return Duration.ofSeconds(Long.parseLong(seconds));
}
}
return null;
}
}
Redis序列化类,使用FastJson序列化
import com.alibaba.fastjson2.JSON;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.lang.reflect.Type;
/**
* @author fly_roushan
* 2023/4/10 14:06
*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
private final Type type;
public FastJsonRedisSerializer(Type type) {
this.type = type;
}
@Override
public byte[] serialize(T t) throws SerializationException {
return JSON.toJSONBytes(t);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
return JSON.parseObject(bytes, type);
}
}
用来临时保存注解信息和返回类型
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.cache.annotation.Cacheable;
import java.lang.reflect.Type;
/**
* @author fly_roushan
* 2023/5/12 17:17
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class CacheInfo {
/**
* 返回值类型
*/
private Type returnType;
/**
* 注解信息
*/
private Cacheable cacheable;
}
Redis配置,自定义keyGenerate,不建议使用keyGenerate,命名不清晰,没有逻辑
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.util.ReflectionUtils;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @author fly_roushan
* 2023/5/10 15:35
*/
@Configuration
@EnableCaching(proxyTargetClass = true)
public class SpringCacheConfig extends CachingConfigurerSupport {
@Resource
private ApplicationContext applicationContext;
/**
* SpringCache默认设置为有锁写入
* 可根据cacheName自定义过期时间
* cacheName = "cacheName#60"
* #后为值,单位为每秒
*
* @param redisConnectionFactory factory
* @return cacheManager
*/
@Bean
public CacheManager shortCacheManager(RedisConnectionFactory redisConnectionFactory) {
return new CustomizerRedisCacheManager(RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory), RedisCacheConfiguration.defaultCacheConfig(), initTypeMap());
}
/**
* 指定固定key,不要依赖生成器
*
* @return key generator
*/
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> "";
}
// 获取使用@Cacheable的方法信息
private Map<String, CacheInfo> initTypeMap() {
Map<String, CacheInfo> cacheConfigMap = new HashMap<>(32);
Arrays.stream(applicationContext.getBeanNamesForType(Object.class))
.map(applicationContext::getType).filter(Objects::nonNull)
.forEach(clazz -> ReflectionUtils.doWithMethods(clazz, method -> {
ReflectionUtils.makeAccessible(method);
Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable.class);
if (Objects.nonNull(cacheable)) {
for (String cache : cacheable.cacheNames()) {
cacheConfigMap.put(cache, new CacheInfo(method.getGenericReturnType(), cacheable));
}
}
})
);
return cacheConfigMap;
}
}