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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 通过切面结合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 中的用法
目录
相关文章
|
4月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
12天前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
77 1
Redis专题-实战篇二-商户查询缓存
|
4月前
|
缓存 NoSQL Java
Redis+Caffeine构建高性能二级缓存
大家好,我是摘星。今天为大家带来的是Redis+Caffeine构建高性能二级缓存,废话不多说直接开始~
716 0
|
12天前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
2月前
|
缓存 Java 数据库连接
怎么使用注解开启二级缓存,注解应该放在那里?
在 MyBatis 中,使用 `@CacheNamespace` 注解可开启二级缓存,该注解应添加在 Mapper 接口上。通过配置 `eviction`、`flushInterval`、`size` 等参数,可以控制缓存行为。此外,实体类需实现 `Serializable` 接口以确保缓存正常工作。
101 1
|
4月前
|
消息中间件 缓存 NoSQL
基于Spring Data Redis与RabbitMQ实现字符串缓存和计数功能(数据同步)
总的来说,借助Spring Data Redis和RabbitMQ,我们可以轻松实现字符串缓存和计数的功能。而关键的部分不过是一些"厨房的套路",一旦你掌握了这些套路,那么你就像厨师一样可以准备出一道道饕餮美食了。通过这种方式促进数据处理效率无疑将大大提高我们的生产力。
186 32
|
4月前
|
缓存 NoSQL Java
Redis:现代服务端开发的缓存基石与电商实践-优雅草卓伊凡
Redis:现代服务端开发的缓存基石与电商实践-优雅草卓伊凡
105 5
Redis:现代服务端开发的缓存基石与电商实践-优雅草卓伊凡
|
6月前
|
缓存 NoSQL Java
Redis应用—8.相关的缓存框架
本文介绍了Ehcache和Guava Cache两个缓存框架及其使用方法,以及如何自定义缓存。主要内容包括:Ehcache缓存框架、Guava Cache缓存框架、自定义缓存。总结:Ehcache适合用作本地缓存或与Redis结合使用,Guava Cache则提供了更灵活的缓存管理和更高的并发性能。自定义缓存可以根据具体需求选择不同的数据结构和引用类型来实现特定的缓存策略。
394 16
Redis应用—8.相关的缓存框架
|
6月前
|
缓存 监控 NoSQL
Redis--缓存击穿、缓存穿透、缓存雪崩
缓存击穿、缓存穿透和缓存雪崩是Redis使用过程中可能遇到的常见问题。理解这些问题的成因并采取相应的解决措施,可以有效提升系统的稳定性和性能。在实际应用中,应根据具体场景,选择合适的解决方案,并持续监控和优化缓存策略,以应对不断变化的业务需求。
1281 29
|
9月前
|
存储 缓存 NoSQL
解决Redis缓存数据类型丢失问题
解决Redis缓存数据类型丢失问题
379 85