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

本文涉及的产品
云数据库 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
目录
相关文章
|
5月前
|
存储 缓存 NoSQL
SSM整合Redis&注解式缓存的使用
SSM整合Redis&注解式缓存的使用
155 1
|
4月前
|
缓存 NoSQL Java
SSM之spring注解式缓存redis
SSM之spring注解式缓存redis
39 0
|
5月前
|
缓存 NoSQL Java
Redis之与SSM集成Spring注解式缓存
Redis之与SSM集成Spring注解式缓存
62 0
|
4月前
|
缓存 NoSQL Java
Spring Data Redis对象缓存序列化问题
在使用 Redis 时,有没有遇到同我一样,对象缓存序列化问题的呢?
67 6
Spring Data Redis对象缓存序列化问题
|
4月前
|
缓存 Java Maven
Spring Cache框架,实现了基于注解的缓存功能。
Spring Cache框架,实现了基于注解的缓存功能。
33 0
|
10月前
|
NoSQL Java Redis
如何使用注解来实现 Redis 分布式锁的功能?
如何使用注解来实现 Redis 分布式锁的功能?
127 0
|
缓存 NoSQL 网络协议
利用Java来访问Redis并对Redis进行相关操作以及spring+redis集成配置与注解式注解
redis缓存的一些注意事项 只应将热数据放到缓存中 所有缓存信息都应设置过期时间 缓存过期时间应当分散以避免集中过期 缓存key应具备可读性 应避免不同业务出现同名缓存key 可对key进行适当的缩写以节省内存空间 选择合适的数据结构 确保写入缓存中的数据是完整且正确的 避免使用耗时较长的操作命令,如:keys * Redis默认配置中操作耗时超过10ms即视为慢查询 一个key对应的数据不应过大 对于string类型,一个key对应的value大小应控制在10K以内,1K左右更优hash类型,不应超过5000行
|
缓存 NoSQL Java
基于注解实现缓存的框架 -- SpringCache
Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能,大大简化我们在业务中操作缓存的代码。
基于注解实现缓存的框架 -- SpringCache
|
存储 缓存 NoSQL
(二)springboot整合redis,基于注解快速实现缓存功能
缓存主要是将数据存在计算机的内存当中,以便于在使用的时候是可以实现快速读取使用,它的快也是相对于硬盘读取而言。 Redis 是一个开源(BSD 许可)的内存中数据结构存储,用作数据库、缓存、消息代理和流引擎。Redis 提供数据结构,例如字符串、散列、列表、集合、带有范围查询的排序集、位图、超日志、地理空间索引和流。 redis是一种使用比较广泛、性能强悍的缓存框架,在国内公司的使用量也是很多的。......
239 0
(二)springboot整合redis,基于注解快速实现缓存功能
|
XML 存储 SQL
SSM 整合redis,使用AOP实现缓存过滤器
SSM 整合redis,使用AOP实现缓存过滤器
129 0
SSM 整合redis,使用AOP实现缓存过滤器