通过切面结合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 中的用法
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
13天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
15天前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
19天前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
8天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
24 5
|
23天前
|
缓存 NoSQL Redis
Redis 缓存使用的实践
《Redis缓存最佳实践指南》涵盖缓存更新策略、缓存击穿防护、大key处理和性能优化。包括Cache Aside Pattern、Write Through、分布式锁、大key拆分和批量操作等技术,帮助你在项目中高效使用Redis缓存。
128 22
|
22天前
|
缓存 NoSQL 中间件
redis高并发缓存中间件总结!
本文档详细介绍了高并发缓存中间件Redis的原理、高级操作及其在电商架构中的应用。通过阿里云的角度,分析了Redis与架构的关系,并展示了无Redis和使用Redis缓存的架构图。文档还涵盖了Redis的基本特性、应用场景、安装部署步骤、配置文件详解、启动和关闭方法、systemctl管理脚本的生成以及日志警告处理等内容。适合初学者和有一定经验的技术人员参考学习。
121 7
|
20天前
|
SQL 缓存 Java
MyBatis如何关闭一级缓存(分注解和xml两种方式)
MyBatis如何关闭一级缓存(分注解和xml两种方式)
60 5
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
|
1月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
77 6
下一篇
无影云桌面