自学内容网 自学内容网

【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)!