1.说明
因为原生的@Cacheable 注解无法声明缓存时间,所以在这里可以自定义一个缓存注解。在声明缓存注解的过程中使用了redis,因为在上一篇文章中已经说明了Spring Boot如何整合redis组件了,所以在此就不做过多的说明了。
2.步骤
1.声明@RedisCache注解
/*** @description: redis缓存注解* @author: xiaYZ* @createDate: 2022/5/24*/@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface RedisCache {//缓存名称String value() default "";//缓存的键名String[] key() default {};//过期时间,默认1小时,单位秒int expiredTime() default 3600;}
2.针对@RedisCache注解进行说明和预处理
/*** @author 10412*/@Component@Aspectpublic class RedisCacheAspect {/*** redis操作工具类*/@Resourceprivate RedisUtil redisUtil;/*** redission分布式锁*/@Resourceprivate RedissonClient redissonClient;@Around("@annotation(com.fwy.common.annotation.redis.RedisCache)")public Object cacheAroundAdvice(ProceedingJoinPoint point) throws Throwable {// 获得连接点参数Object[] args = point.getArgs();// 声明一个对象Object,为方法的返回结果Object result = null;// 通过反射获得原始方法信息MethodSignature signature = (MethodSignature)point.getSignature();Type returnType = signature.getMethod().getGenericReturnType();RedisCache redisCache = signature.getMethod().getAnnotation(RedisCache.class);//获取缓存名称String key = redisCache.value();//获取缓存keyString[] keys = redisCache.key();//获取缓存时间单位sint expireTime = redisCache.expiredTime();// 根据注解拼接缓存keyif (keys.length != 0) {key += ":" + StringUtils.join(keys,":");}if(args.length != 0){key += ":" + StringUtils.join(args,":");}// 缓存代码result = cacheHit(returnType, key);// 表示缓存不为空,则直接返回数据if (result != null){return result;}// 使用redisson获得分布式锁RLock lock = redissonClient.getLock(key + ":lock");Properties prop = new Properties();try {// 成功拿到分布式锁的,可以查询dbboolean b = lock.tryLock(100, 10000, TimeUnit.SECONDS);if(b){// 执行连接点方法,查询dbresult = point.proceed(args);// 如果查询不到数据,将空对象放入缓存,防止缓存穿透if(result == null){redisUtil.set(key, JSON.toJSONString(new Object()),expireTime);return null;}// 查询到数据后同步缓存返回结果redisUtil.set(key, JSON.toJSONString(result),expireTime);// 返回结果给原始方法return result;}else {// 如果没有拿到分布式锁,那么说明已经有人查数据库了,当前执行的线程直接取缓存里面拿其他线程已经存入的数据就行了// 等待其他线程放入数据Thread.sleep(1000);return cacheHit(returnType,key);}} catch (Throwable throwable) {throwable.printStackTrace();}finally {if (lock.isLocked()){lock.unlock();}}// 返回原方法需要的的结果return result;}/**** 查询缓存中的key* @param returnType* @param key* @return*/private Object cacheHit(Type returnType, String key) {Object result = null;String cache = (String)redisUtil.get(key);if(StringUtils.isNotBlank(cache)){result = JSON.parseObject(cache, returnType);}return result;}}
3.redis序列化处理
@Configuration@EnableCachingpublic class RedisConfig {@Beanpublic KeyGenerator wiselyKeyGenerator() {return new KeyGenerator() {@Overridepublic Object generate(Object target, Method method, Object... params) {StringBuilder sb = new StringBuilder();sb.append(target.getClass().getName());sb.append(method.getName());for (Object obj : params) {sb.append(obj.toString());}return sb.toString();}};}@Beanpublic RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);//序列号key valueredisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);redisTemplate.afterPropertiesSet();return redisTemplate;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer<String> redisSerializer = new StringRedisSerializer();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);//设置默认缓存过期时间RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()// 默认没有特殊指定的,,1天.entryTtl(Duration.ofHours(1)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();return RedisCacheManager.builder(factory).cacheDefaults(config).build();}}
4.redis缓存工具类
5.使用
//value缓存键值,expiredTime缓存时间@Override@RedisCache(value = "questionBank",expiredTime = 60*60*24*7)public List<QuestionBank> findAllQuestionBank() {List<QuestionBank> allQuestionBank = studyDao.findAllQuestionBank();allQuestionBank.forEach(question ->{question.setOptions(question.getOptions().replaceAll("\n",""));});return allQuestionBank;}
