1.说明

因为原生的@Cacheable 注解无法声明缓存时间,所以在这里可以自定义一个缓存注解。在声明缓存注解的过程中使用了redis,因为在上一篇文章中已经说明了Spring Boot如何整合redis组件了,所以在此就不做过多的说明了。

2.步骤

1.声明@RedisCache注解

  1. /**
  2. * @description: redis缓存注解
  3. * @author: xiaYZ
  4. * @createDate: 2022/5/24
  5. */
  6. @Target(ElementType.METHOD)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface RedisCache {
  9. //缓存名称
  10. String value() default "";
  11. //缓存的键名
  12. String[] key() default {};
  13. //过期时间,默认1小时,单位秒
  14. int expiredTime() default 3600;
  15. }

2.针对@RedisCache注解进行说明和预处理

  1. /**
  2. * @author 10412
  3. */
  4. @Component
  5. @Aspect
  6. public class RedisCacheAspect {
  7. /**
  8. * redis操作工具类
  9. */
  10. @Resource
  11. private RedisUtil redisUtil;
  12. /**
  13. * redission分布式锁
  14. */
  15. @Resource
  16. private RedissonClient redissonClient;
  17. @Around("@annotation(com.fwy.common.annotation.redis.RedisCache)")
  18. public Object cacheAroundAdvice(ProceedingJoinPoint point) throws Throwable {
  19. // 获得连接点参数
  20. Object[] args = point.getArgs();
  21. // 声明一个对象Object,为方法的返回结果
  22. Object result = null;
  23. // 通过反射获得原始方法信息
  24. MethodSignature signature = (MethodSignature)point.getSignature();
  25. Type returnType = signature.getMethod().getGenericReturnType();
  26. RedisCache redisCache = signature.getMethod().getAnnotation(RedisCache.class);
  27. //获取缓存名称
  28. String key = redisCache.value();
  29. //获取缓存key
  30. String[] keys = redisCache.key();
  31. //获取缓存时间单位s
  32. int expireTime = redisCache.expiredTime();
  33. // 根据注解拼接缓存key
  34. if (keys.length != 0) {
  35. key += ":" + StringUtils.join(keys,":");
  36. }
  37. if(args.length != 0){
  38. key += ":" + StringUtils.join(args,":");
  39. }
  40. // 缓存代码
  41. result = cacheHit(returnType, key);
  42. // 表示缓存不为空,则直接返回数据
  43. if (result != null){
  44. return result;
  45. }
  46. // 使用redisson获得分布式锁
  47. RLock lock = redissonClient.getLock(key + ":lock");
  48. Properties prop = new Properties();
  49. try {
  50. // 成功拿到分布式锁的,可以查询db
  51. boolean b = lock.tryLock(100, 10000, TimeUnit.SECONDS);
  52. if(b){
  53. // 执行连接点方法,查询db
  54. result = point.proceed(args);
  55. // 如果查询不到数据,将空对象放入缓存,防止缓存穿透
  56. if(result == null){
  57. redisUtil.set(key, JSON.toJSONString(new Object()),expireTime);
  58. return null;
  59. }
  60. // 查询到数据后同步缓存返回结果
  61. redisUtil.set(key, JSON.toJSONString(result),expireTime);
  62. // 返回结果给原始方法
  63. return result;
  64. }else {
  65. // 如果没有拿到分布式锁,那么说明已经有人查数据库了,当前执行的线程直接取缓存里面拿其他线程已经存入的数据就行了
  66. // 等待其他线程放入数据
  67. Thread.sleep(1000);
  68. return cacheHit(returnType,key);
  69. }
  70. } catch (Throwable throwable) {
  71. throwable.printStackTrace();
  72. }finally {
  73. if (lock.isLocked()){
  74. lock.unlock();
  75. }
  76. }
  77. // 返回原方法需要的的结果
  78. return result;
  79. }
  80. /***
  81. * 查询缓存中的key
  82. * @param returnType
  83. * @param key
  84. * @return
  85. */
  86. private Object cacheHit(Type returnType, String key) {
  87. Object result = null;
  88. String cache = (String)redisUtil.get(key);
  89. if(StringUtils.isNotBlank(cache)){
  90. result = JSON.parseObject(cache, returnType);
  91. }
  92. return result;
  93. }
  94. }

3.redis序列化处理

  1. @Configuration
  2. @EnableCaching
  3. public class RedisConfig {
  4. @Bean
  5. public KeyGenerator wiselyKeyGenerator() {
  6. return new KeyGenerator() {
  7. @Override
  8. public Object generate(Object target, Method method, Object... params) {
  9. StringBuilder sb = new StringBuilder();
  10. sb.append(target.getClass().getName());
  11. sb.append(method.getName());
  12. for (Object obj : params) {
  13. sb.append(obj.toString());
  14. }
  15. return sb.toString();
  16. }
  17. };
  18. }
  19. @Bean
  20. public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  21. RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
  22. redisTemplate.setConnectionFactory(redisConnectionFactory);
  23. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  24. ObjectMapper objectMapper = new ObjectMapper();
  25. objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  26. objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  27. jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
  28. //序列号key value
  29. redisTemplate.setKeySerializer(new StringRedisSerializer());
  30. redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
  31. redisTemplate.setHashKeySerializer(new StringRedisSerializer());
  32. redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
  33. redisTemplate.afterPropertiesSet();
  34. return redisTemplate;
  35. }
  36. @Bean
  37. public CacheManager cacheManager(RedisConnectionFactory factory) {
  38. RedisSerializer<String> redisSerializer = new StringRedisSerializer();
  39. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  40. //解决查询缓存转换异常的问题
  41. ObjectMapper om = new ObjectMapper();
  42. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  43. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  44. jackson2JsonRedisSerializer.setObjectMapper(om);
  45. //设置默认缓存过期时间
  46. RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
  47. // 默认没有特殊指定的,,1天
  48. .entryTtl(Duration.ofHours(1))
  49. .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
  50. .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
  51. .disableCachingNullValues();
  52. return RedisCacheManager.builder(factory)
  53. .cacheDefaults(config)
  54. .build();
  55. }
  56. }

4.redis缓存工具类

5.使用

  1. //value缓存键值,expiredTime缓存时间
  2. @Override
  3. @RedisCache(value = "questionBank",expiredTime = 60*60*24*7)
  4. public List<QuestionBank> findAllQuestionBank() {
  5. List<QuestionBank> allQuestionBank = studyDao.findAllQuestionBank();
  6. allQuestionBank.forEach(question ->{
  7. question.setOptions(question.getOptions().replaceAll("\n",""));
  8. });
  9. return allQuestionBank;
  10. }