手写redis@Cacheable注解 支持过期时间设置

本文介绍如何使用Spring AOP及自定义注解@ExtCacheable实现方法级别的Redis缓存功能,包括缓存配置、注解定义及AOP切面处理等关键技术点。

原理解释 
友情链接  手写redis @ Cacheable注解参数java对象作为键值 

@Cacheable注解作用,将带有该注解方法的返回值存放到redis的的中;

使用方法在方法上使用@Cacheable(键=“测试+#P0 + P1#...”)

表示键值为测试+方法第一个参数+方法第二个参数,值值为该方法的返回值。

以下源代码表示获取人员列表,Redis的中存放的关键值为'领袖'+ leaderGroupId + UUID + yearDetailId

        @Override
    @Cacheable(key="'leader'+#p0+#p1+#p2",value="leader")
    public List<Leader> listLeaders(String leaderGroupId, String uuid, String yearDetailId) {
        return sysIndexMapper.listLeaders(leaderGroupId, uuid, yearDetailId);
    }
等同于

        @Override
    public List<Leader> listLeaders(String leaderGroupId, String uuid, String yearDetailId) {
        String key = "leader" + leaderGroupId + uuid + yearDetailId;
        // 判断缓存是否存在redis中
        boolean hasKey = redisUtil.hasKey(key);
        if (hasKey) {
                        //如果存在 返还redis中的值
            Object leadersList = redisUtil.get(key);
            return (List<Leader>) leadersList;
        } else {
            List<Leader> leadersQuotaDetailList = sysIndexMapper.listLeaders(leaderGroupId, uuid, yearDetailId);
                        //将查询结果存放在redis中
            redisUtil.set(key, leadersQuotaDetailList);
            return leadersQuotaDetailList;
        }
    }
说白了就是在原方法的前面判断的关键值是否存在的Redis的中,如果存在就取内存中的值,如果不存在就查询数据库,将查询结果存放在Redis的的中。

实现方法
使用代理模式,在方法执行前和执行后可以添加其他处理程序,本文采用springAOP +注解方式。
集成redis,封装Redis工具类
原版本不支持关键过期时间设置,本文将实现
源代码
缓存配置类RedisConfig

package com.huajie.config;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
 
/**
 * Redis缓存配置类
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
 
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;
 
    // 自定义缓存key生成策略
    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, java.lang.reflect.Method method, Object... params) {
                StringBuffer sb = new StringBuffer();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }
 
    // 缓存管理器
    @Bean
    public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        // 设置缓存过期时间
        cacheManager.setDefaultExpiration(10000);
        return cacheManager;
    }
 
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
 
    private void setSerializer(StringRedisTemplate template) {
        @SuppressWarnings({ "rawtypes", "unchecked" })
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
    }
}
Redis的依赖引入,配置文件,工具类RedisUtil,网上几个版本都类似,本文参考以下版本传送门

https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html

准备工作做好之后开始正式编写注解@Cacheable nextkey()用做二级缓存本文中不会用到

nextKey用法详情> 设计模式(实战) - 责任链模式 <

创建的Java的注解@ExtCacheable  

package com.huajie.annotation;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtCacheable {
    
    String key() default "";
    
    String nextKey() default "";
 
    int expireTime() default 1800;//30分钟
    
}
SpringAop切面CacheableAspect

package com.huajie.aspect;
 
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.huajie.annotation.ExtCacheable;
import com.huajie.utils.RedisUtil;
 
/**
 * redis缓存处理
 * 不适用与内部方法调用(this.)或者private
 */
@Component
@Aspect
public class CacheableAspect {
    
    @Autowired
    private RedisUtil redisUtil;
 
    @Pointcut("@annotation(com.huajie.annotation.ExtCacheable)")
    public void annotationPointcut() {
    }
 
    @Around("annotationPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获得当前访问的class
        Class<?> className = joinPoint.getTarget().getClass();
        // 获得访问的方法名
        String methodName = joinPoint.getSignature().getName();
        // 得到方法的参数的类型
        Class<?>[] argClass = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        Object[] args = joinPoint.getArgs();
        String key = "";
        int expireTime = 1800;
        try {
            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);
            method.setAccessible(true);
            // 判断是否存在@ExtCacheable注解
            if (method.isAnnotationPresent(ExtCacheable.class)) {
                ExtCacheable annotation = method.getAnnotation(ExtCacheable.class);
                key = getRedisKey(args,annotation);
                expireTime = getExpireTime(annotation);
            }
        } catch (Exception e) {
            throw new RuntimeException("redis缓存注解参数异常", e);
        }
        // 获取缓存是否存在
        boolean hasKey = redisUtil.hasKey(key);
        if (hasKey) {
            return redisUtil.get(key);
        } else {
                         //执行原方法(java反射执行method获取结果)
            Object res = joinPoint.proceed();
                         //设置缓存
            redisUtil.set(key, res);
                         //设置过期时间
            redisUtil.expire(key, expireTime);
            return res;
        }
    }
    
    private int getExpireTime(ExtCacheable annotation) {
        return annotation.expireTime();
    }
 
    private String getRedisKey(Object[] args,ExtCacheable annotation) {
        String primalKey = annotation.key();
        //获取#p0...集合
        List<String> keyList = getKeyParsList(primalKey);
        for (String keyName : keyList) {
            int keyIndex = Integer.parseInt(keyName.toLowerCase().replace("#p", ""));
            Object parValue = args[keyIndex];
            primalKey = primalKey.replace(keyName, String.valueOf(parValue));
        }
        return primalKey.replace("+","").replace("'","");
    }
 
    // 获取key中#p0中的参数名称
    private static List<String> getKeyParsList(String key) {
        List<String> ListPar = new ArrayList<String>();
        if (key.indexOf("#") >= 0) {
            int plusIndex = key.substring(key.indexOf("#")).indexOf("+");
            int indexNext = 0;
            String parName = "";
            int indexPre = key.indexOf("#");
            if(plusIndex>0){
                indexNext = key.indexOf("#") + key.substring(key.indexOf("#")).indexOf("+");
                parName = key.substring(indexPre, indexNext);
            }else{
                parName = key.substring(indexPre);
            }
            ListPar.add(parName.trim());
            key = key.substring(indexNext + 1);
            if (key.indexOf("#") >= 0) {
                ListPar.addAll(getKeyParsList(key));
            }
        }
        return ListPar;
    }
    
}
业务模块使用方法

@Override
    @ExtCacheable(key = "Leaders+#p0+#p1+#p2")
    // 手机端获取领导人员列表
    public List<Leader> listLeaders(String leaderGroupId, String uuid, String yearDetailId) {
        List<Leader> leadersQuotaDetailList = sysIndexMapper.listLeaders(leaderGroupId, uuid, yearDetailId);
        return leadersQuotaDetailList;
    }
业务模块过期时间使用方法,5分钟过期

@Override
    @ExtCacheable(key = "mobileCacheFlag", expireTime = 60 * 5)
    public int cacheFlag() {
        int mobileCacheFlag = 1;
        mobileCacheFlag = sysIndexMapper.cacheFlag();
        return mobileCacheFlag;
    }

Redis的的截图

### 关于 `@Cacheable` 注解设置过期时间常见问题与解决方法 #### 设置全局和局部过期时间冲突 当在应用程序中既设置了全局缓存过期时间又针对特定缓存名称设置了不同过期时间时,可能会遇到冲突情况。通常情况下,更具体的配置会覆盖较通用的设定。例如,在 YAML 文件里指定全局默认超时时,可以同时为某些特别命名的空间提供个性化的 TTL (Time To Live),这样就不会影响到其他未指明TTL值的数据项[^4]。 ```yaml spring: cache: cache-names: caches redis: time-to-live: 10m # 全局缓存过期时间为10分钟 time-to-live: "cache1": 5m # 特定缓存'cache1'的过期时间为5分钟 ``` #### 缓存更新后旧数据仍然存在一段时间 有时即使已经成功刷新了某个被标记有 `@Cacheable` 方法的结果集,但由于之前版本的数据尚未达到其预设的有效期限而继续保留着。为了避免这种情况发生,可以通过调整策略使新产生的记录立即生效或将现有条目提前淘汰掉。一种做法是在每次调用服务端点前先清除关联键对应的存储位置;另一种则是利用条件参数控制是否允许读取已有副本[^3]。 ```java // 清除特定key下的所有缓存 @CacheEvict(value="users", allEntries=true) public void clearUserCaches() {} // 或者基于表达式的删除操作 @CachePut(value="items", key="#id") public Item updateItem(@PathVariable Long id, ...) { ... } ``` #### 动态计算缓存有效期 对于一些场景下可能希望依据业务逻辑动态决定每笔交易应该保存多久,则可以在使用 `@Cacheable` 注解的同时传递额外属性给它——比如通过 SpEL 表达式来获取当前上下文中可用的信息作为输入源之一参与运算得出最终结果。这使得开发者能够更加灵活地管理资源生命周期而不必受限于固定的数值范围之内[^2]。 ```java @Cacheable( value = {"products"}, unless = "#result == null", sync = true, condition = "@environment.getProperty('feature.toggle.dynamic.expiry')=='true'", keyGenerator = "customKeyGen" ) public Product getProductById(Long productId){ return productRepository.findById(productId).orElse(null); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值