通过切面结合Redis自定义缓存注解

简介: 通过切面结合Redis自定义缓存注解

问题

你每次用Redis做缓存的时候是不是都要写这么一大堆重复的代码?

String result = jedis.get(key);
    if(StringUtils.isEmpty(result)){
      // 查询数据库,并将结果存入Redis
    }else {
      return result;
    }

接下来看我怎么解决这个问题,去除重复的代码,以后每次用缓存的时候只需要添加一个注解就ok了

解决方案

  • redis配置(集群)
redis:
#    host: 127.0.0.1
#    port: 6379
#    timeout: 60000
    jedis:
      pool.max-active: 8
    cluster:
      nodes: 192.168.174.128:7000,192.168.174.128:7001,192.168.174.128:7002,192.168.174.128:7003,192.168.174.128:7004,192.168.174.128:7005
  • 配置类
@Configuration
public class RedisConfig {
    @Value("${spring.redis.cluster.nodes}")
    private String nodes;
    /*单机模式*/
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        // key序列化
        RedisSerializer<?> stringSerializer = new StringRedisSerializer();
        // value序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        redisTemplate.setKeySerializer(stringSerializer);// key序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
        redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
    /*集群模式*/
    @Bean
    public JedisCluster jedisCluster() {
        Set<HostAndPort> setNodes = new HashSet<>();
        String[] arrayNode = nodes.split(",");
        //node{IP:PORT}
        for (String node : arrayNode) {
            String host = node.split(":")[0];
            int port = Integer.parseInt(node.split(":")[1]);
            setNodes.add(new HostAndPort(host, port));
        }
        return new JedisCluster(setNodes);
    }
}
  • 自定义注解
/**
 * 该注解主要实现查询操作. 
 * 有缓存查询缓存,没缓存查询数据库
 * @author zhushanglin
 * 
 * 操作规范:
 *  key: 
 *    1.用户没有赋值
 *      如果key为"",表示用户使用自动生成的key
 *      key:包名.类名.方法名.拼接第一个参数
 *    2.如果用户赋值
 *      key:使用用户的数据
 *  seconds:
 *    如果时间不为0,表示用户需要设定超时时间
 */
@Target({ElementType.METHOD}) //对方法生效
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache_Find {
  String key() default "";
  int  seconds() default 0;
}
  • 注解的切面(对使用该注解的处理)
    这里使用的是Redis集群,Redis集群搭建可以参考Redis集群搭建
@Aspect     //标识切面
@Component    //交给spring容器管理
public class CacheAspect {
  
  //required = false 当用户使用时才注入
  @Autowired(required = false)
  private JedisCluster jedis; //注入redis集群对象
  
  /**
   * 利用AOP规则:动态获取注解对象
   * 步骤:
   *  1.根据key查询redis.
   *  2.没有数据,需要让目标方法执行.查询的结果保存redis
   *  3.将json串转化为返回值对象 返回.
   * @param joinPoint
   * @param cacheFind
   * @return
   */
  @SuppressWarnings("unchecked")
  @Around("@annotation(cacheFind)")
  public Object around(ProceedingJoinPoint joinPoint,
      Cache_Find cacheFind) {
    Object data = null;
    
    String key = getKey(joinPoint,cacheFind);
    //1.从redis中获取数据
    String result = jedis.get(key);
    //2.判断缓存中是否有数据
    try {
      if(StringUtils.isEmpty(result)) {
        //2.1缓存中没有数据
        data = joinPoint.proceed();//目标方法执行
        //2.2将返回值结果,转化为JSON
        String json = ObjectMapperUtil.toJSON(data);
        if(StringUtils.isEmpty(json)){
          // 把空的数据也缓存起来,并设置过期时间,预防缓存穿透!
          jedis.setex(key,60, json);
        }else {
          //2.3判断用户是否编辑时间
          //如果有时间,必须设定超时时间.
          if(cacheFind.seconds()>0) {
            int seconds = cacheFind.seconds();
            jedis.setex(key,seconds, json);
          }else {
            jedis.set(key,json);
          }
        }
        System.out.println("AOP查询数据库!!!!!");
      }else {
        //表示缓存数据不为空,将缓存数据转化为对象
        Class returnClass = getReturnClass(joinPoint);
        data = ObjectMapperUtil.toObject(result,returnClass);
        System.out.println("AOP查询缓存!!!!");
      }
    } catch (Throwable e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
    
    return data;
  }
  
  /**
   * 获取目标方法的返回值类型
   * @param joinPoint
   * @return
   */
  private Class getReturnClass(ProceedingJoinPoint joinPoint) {
    
    MethodSignature signature = 
        (MethodSignature) joinPoint.getSignature();
    return signature.getReturnType();
  }
  /**
   * 动态获取key
   * @param joinPoint
   * @param cacheFind
   * @return
   */
  private String getKey(ProceedingJoinPoint joinPoint, Cache_Find cacheFind) {
    String key = cacheFind.key();
    if(StringUtils.isEmpty(key)) {
      //key自动生成 
      String className = 
          joinPoint.getSignature().getDeclaringTypeName();
      String methodName = 
          joinPoint.getSignature().getName();
      if(joinPoint.getArgs().length>0)
        //拼接第一个参数
        key = className+"."+methodName+"::"
          + joinPoint.getArgs()[0];
      else 
        key = className+"."+methodName;
    }
    
    return key;
  }
}

上面使用到的ObjectMapperUtil工具类是:

import java.io.IOException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
 * 作用: 实现对象与JSON串之间的转化
 * @author zhushanglin
 *
 */
public class ObjectMapperUtil {
  
  //常量对象 可以调用对象的方法 线程安全 不安全???
  private static final ObjectMapper MAPPER = new ObjectMapper();
  
  /**
   * 将检查异常转化为运行时异常.
   * @param data
   * @return
   */
  public static String toJSON(Object data) {
    String json = null;
    try {
      json = MAPPER.writeValueAsString(data);
    } catch (JsonProcessingException e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
    
    return json;
  }
  
  
  /**
   * 根据JSON转化为对象 
   * 参数:json数据,Class
   * 返回值:由用户决定.
   */
  @SuppressWarnings("unchecked")
  public static <T> T toObject(String json,Class<T> target) {
    T obj = null;
    try {
      obj = MAPPER.readValue(json, target);
    } catch (IOException e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
    return obj;
  }
}

注解的使用

在需要使用Redis缓存的方法上 使用@Cache_Find注解即可,这样再也不用每次都写文章最开始的那个if else 了,是不是很实用,有用的话帮忙点个赞哦~

@Override
  @Cache_Find//实现缓存处理
  public ItemDesc findItemDescById(Long id) {
    String url = "http://manage.jt.com/web/item/findItemDescById/"+id;
    String itemDescJSON = httpClient.doGet(url);
    return ObjectMapperUtil.toObject(itemDescJSON, ItemDesc.class);
  }

分布式存取

@Autowired
    private JedisCluster jedisCluster;
  jedisCluster.setex("cookie", 20*60, cookie);
  String cookie = jedisCluster.get("cookie");
  // 其他对象类型,参考CacheAspect 中的用法
目录
相关文章
|
8月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
3月前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。
|
4月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
236 1
Redis专题-实战篇二-商户查询缓存
|
3月前
|
缓存 运维 监控
Redis 7.0 高性能缓存架构设计与优化
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Redis 7.0高性能缓存架构,探索函数化编程、多层缓存、集群优化与分片消息系统,用代码在二进制星河中谱写极客诗篇。
|
4月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
6月前
|
缓存 Java 数据库连接
怎么使用注解开启二级缓存,注解应该放在那里?
在 MyBatis 中,使用 `@CacheNamespace` 注解可开启二级缓存,该注解应添加在 Mapper 接口上。通过配置 `eviction`、`flushInterval`、`size` 等参数,可以控制缓存行为。此外,实体类需实现 `Serializable` 接口以确保缓存正常工作。
158 1
|
存储 缓存 NoSQL
Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存|学习笔记
快速学习 Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存
Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存|学习笔记
|
缓存 NoSQL 安全
6.0Spring Boot 2.0实战 Redis 分布式缓存6.0|学习笔记
快速学习6.0Spring Boot 2.0实战 Redis 分布式缓存6.0。
588 0
6.0Spring Boot 2.0实战 Redis 分布式缓存6.0|学习笔记
|
缓存 NoSQL Redis
首页数据显示-添加 redis 缓存(3)| 学习笔记
快速学习 首页数据显示-添加 redis 缓存(3)
265 0
首页数据显示-添加 redis 缓存(3)| 学习笔记
|
缓存 NoSQL Java
首页数据显示-添加 redis 缓存(1) | 学习笔记
快速学习 首页数据显示-添加 redis 缓存(1)
343 0
首页数据显示-添加 redis 缓存(1) | 学习笔记