【spring cache】自定义redis缓存管理器自控key过期时间
目录
说明
spring cache 缓存自带了很多缓存注解,但是,这些注解并没有对key进行设置过期时间的参数。当我们需要为某些key设置过期时间时,使用spring cache 的注解并不能满足我们的需求。此时我们需要自定义缓存管理器来实现此需求,满足我们使用spring cache 注解的同时能为指定的key加上过期时间。
此文的缓存中间件使用的是redis.
实现思路
@Cacheable 注解在使用时传递的value属性为缓存的名称,我们可以将时间拼接到这个名称中,通过自定义的缓存管理器来实现分割截取时间和名称,截取到的时间则为key的缓存过期TTL的值。
例如:
user:get@25_s
缓存键名为: user:get
TTL值为:25
TTL值的单位:s 秒
缓存键名和TTL值分割符号:@
TTL值和TTL值的单位分割符号:_
实现步骤
创建项目添加依赖
省创建springboot 项目,以下为关键依赖
<!-- Redisson 锁功能 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson-spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>${spring-boot-starter-cache.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring-boot-starter-data-redis.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>${commons-pool2.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
创建自定义缓存管理器
自定义缓存管理器名称:ThreeCacheManager
import org.springframework.data.redis.cache.*;
import org.springframework.util.StringUtils;
import java.time.Duration;
/**
* 说明:
* 自定义 缓存管理器
* 支持再缓存名称中设置 过期时间
* 格式: 缓存名称@时间值_单位
* 单位支持:(单位区分大小写)
* y:年
* M:月
* d:日
* h:小时
* m:分
* s:秒,默认
* S :毫秒
*
* @author 张小三
* @create 2024-12-05 11:46
* @verson 1.0.0
*/
public class ThreeCacheManager extends RedisCacheManager {
private final static String namePrefix = "three";
/**
* 说明: 自定义缓存时分割符号
*
* @author zhangxiaosan
* @create 2024/12/5
* @param
* @return
*/
private final static String cacheNameTTLSplit = "@";
/**
* 说明: 重写缓存创建逻辑
*
* @param
* @return
* @author zhangxiaosan
* @create 2024/12/5
*/
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfiguration) {
// 使用分割符号分割缓存名称和过期时间
String[] split = name.split(cacheNameTTLSplit);
if (split.length <= 1) {
// 返回默认缓存管理器创建的缓存,没有过期时间
return super.createRedisCache(name, cacheConfiguration);
}
String cacheName = split[0]; //第1部分为缓存名
String ttlValue = split[1]; //第2部分为缓存过期时间
if (!StringUtils.hasText(ttlValue)) {
// 返回默认缓存管理器创建的缓存,有分割符号但是没有过期时间
return super.createRedisCache(name, cacheConfiguration);
}
String[] ttl_type = ttlValue.split("_");
if (ttl_type.length <= 1) {
// 时间和单位分割符不存在,则返回默认缓存管理器创建的缓存
return super.createRedisCache(name, cacheConfiguration);
}
String ttl = ttl_type[0];
if (!StringUtils.hasText(ttl)) {
// 时间不存在,则返回默认缓存管理器创建的缓存
return super.createRedisCache(name, cacheConfiguration);
}
Duration duration = null;
String type = ttl_type[1];// 时间单位
if (!StringUtils.hasText(type)) {
// 时间和单位分割符不存在,则默认为秒
duration = Duration.ofSeconds(Long.parseLong(ttl));
}
switch (type) {
case "y": // 年
duration = Duration.ofDays(Long.parseLong(ttl) * 365);
break;
case "M": // 月
duration = Duration.ofDays(Long.parseLong(ttl) * 30);
break;
case "d": // 日
duration = Duration.ofDays(Long.parseLong(ttl));
break;
case "h": // 小时
duration = Duration.ofHours(Long.parseLong(ttl));
break;
case "m": // 分钟
duration = Duration.ofMinutes(Long.parseLong(ttl));
break;
case "S": // 毫秒
duration = Duration.ofMillis(Long.parseLong(ttl));
break;
default: // 默认。秒
duration = Duration.ofSeconds(Long.parseLong(ttl));
break;
}
// 配置缓存
cacheConfiguration = cacheConfiguration
.computePrefixWith(item -> namePrefix+":"+cacheName + ":")
.entryTtl(duration);
return super.createRedisCache(name, cacheConfiguration);
}
/**
* 说明: 自定义缓存管理器的构造器
*
* @param
* @return
* @author zhangxiaosan
* @create 2024/12/5
*/
public ThreeCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
}
定义redis配置
import io.lettuce.core.ClientOptions;
import io.lettuce.core.SocketOptions;
import io.lettuce.core.TimeoutOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleCacheResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig implements CachingConfigurer {
@Autowired
private RedisProperties redisProperties;
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJson2JsonRedisSerializer<Object> serializer = new FastJson2JsonRedisSerializer<>(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
@Override
public CacheManager cacheManager() {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.disableCachingNullValues() //禁用缓存空值
//.entryTtl(Duration.ofHours(1)) // 设置缓存过期时间为1小时
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new FastJson2JsonRedisSerializer<>(Object.class)));
/*return RedisCacheManager.builder(redisConnectionFactory())
.cacheDefaults(config)
.build();*/
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory());
return new ThreeCacheManager(redisCacheWriter,config);
}
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append("#");
sb.append(method.getName());
for (Object param : params) {
sb.append(param.toString());
}
return sb.toString();
};
}
@Override
public CacheResolver cacheResolver() {
return new SimpleCacheResolver(cacheManager());
}
@Override
public CacheErrorHandler errorHandler() {
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
// 处理获取缓存错误
throw new RuntimeException("redis 获取缓存异常,key:"+key,exception);
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
// 处理放入缓存错误
throw new RuntimeException("redis 放入缓存异常,key:"+key,exception);
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
// 处理清除缓存错误
throw new RuntimeException("redis 清除缓存异常,key:"+key,exception);
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
// 处理清空缓存错误
throw new RuntimeException("redis 清空缓存异",exception);
}
};
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
redisConfig.setHostName(redisProperties.getHost());
redisConfig.setPort(redisProperties.getPort());
redisConfig.setPassword(redisProperties.getPassword());
redisConfig.setDatabase(redisProperties.getDatabase());
// 创建 LettuceClientConfiguration
LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
.clientOptions(ClientOptions.builder()
.socketOptions(SocketOptions.builder().connectTimeout(Duration.ofMillis(redisProperties.getTimeout().toMillis())).build())
.timeoutOptions(TimeoutOptions.enabled())
.build())
.build();
// 创建 LettuceConnectionFactory
LettuceConnectionFactory factory = new LettuceConnectionFactory(redisConfig, clientConfig);
factory.afterPropertiesSet();
return factory;
}
}
redis 缓存值格式序列化
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
public FastJson2JsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
}
}
redis 操作方法(可省略)
package www.three.components.redis.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 说明:
* redis的操作类
* @author 张小三
* @create 2024-10-21 14:08
* @verson 1.0.0
*/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisService {
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public 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 boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获取有效时间
*
* @param key Redis键
* @return 有效时间
*/
public long getExpire(final String key) {
return redisTemplate.getExpire(key);
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection) {
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
}
使用
在 service 的实现方法中使用
// 键名为user:get的数据缓存25秒,25秒过后自动从redis中删除
@Cacheable(value = "user:get@25_s", key = "#param.id",unless = "#result==null")
public User getUserById(User param) { ... }
通过客户端查询,验证结果
spring cache 缓存注解
@Cacheable
说明
用于标记一个方法,表示该方法的结果可以被缓存。Spring 会检查缓存中是否已有该方法的结果,如果有,就直接返回缓存中的数据,否则执行方法并将结果存入缓存
参数
value 或者 cacheNames
描述
指定缓存的名称。一个或多个缓存名称,可以指定多个缓存区域来存储数据。
类型
String[] 或者String
示例
@Cacheable(value = "users")
public User getUserById(Long userId) { ... }
key
描述
指定缓存的键。通常是一个 SpEL(Spring Expression Language)表达式,用来动态生成缓存的键。
类型
String
示例
@Cacheable(value = "users", key = "#userId")
public User getUserById(Long userId) { ... }
keyGenerator
描述
指定自定义的 KeyGenerator 来生成缓存键。如果设置了 key,keyGenerator 会被忽略。
类型
String
示例
@Cacheable(value = "users", keyGenerator = "customKeyGenerator")
public User getUserById(Long userId) { ... }
condition
描述
SpEL 表达式,只有在满足某个条件时,才会将方法的返回值缓存。如果条件为 false,缓存将不会存储该值。
类型
String
示例
@Cacheable(value = "users", condition = "#userId > 100")
public User getUserById(Long userId) { ... }
unless
描述
SpEL 表达式,用于排除某些缓存的情况。当表达式为 true 时,缓存不会存储返回值。
类型
String
示例
@Cacheable(value = "users", unless = "#result == null")
public User getUserById(Long userId) { ... }
cacheManager
描述
指定使用的 CacheManager,可以通过不同的缓存管理器管理缓存。
类型
String
示例
@Cacheable(value = "users", cacheManager = "customCacheManager")
public User getUserById(Long userId) { ... }
cacheResolver
描述
指定自定义的 CacheResolver,用于决定使用哪个缓存。通常在复杂的缓存场景中使用。
类型
String
示例:
@Cacheable(value = "users", cacheResolver = "customCacheResolver")
public User getUserById(Long userId) { ... }
sync
描述
指定是否启用同步缓存行为。在多个线程并发访问相同缓存时,启用同步会确保只有一个线程可以执行该方法,其他线程等待方法执行完成后再返回结果。
类型
boolean
默认值
false
示例:
@Cacheable(value = "users", sync = true)
public User getUserById(Long userId) { ... }
原文地址:https://blog.csdn.net/qq_38313548/article/details/144271917
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!