AOP结合redis实现自动存取

Redis对业务层数据缓存 问题引入 开发中,一些业务查询到的数据并不经常改变(如一些网站的热销榜单….),但每次都去操作数据层获取数据,这样就会影响系统性能,响应速度。为了提高查询效率,可以对一些不常变更的业务层数据进行Redis缓存。 缓存数据的数据结构设计 由于缓存数据应该是分模块的,每个模块的每个方法缓存的数据又不一样,基于这种情况,采用Redis的HASH类型存储比较好,每个模块抽象为Hashkey,模块下的... 阅读详情

Redis自定义注解@RedisSimpleCache

注解的作用

在使用过程中,不需要进行Redis中是否有数据的判断,就如同正常的查询数据库一般,当内容在数据库中不存在的情况下,会执行代码,在得到结果之后,再将数据存储中Redis中

注解的实现

使用Aop的技术,在方法中执行前查询数据,发现数据没有存在于数据库中,执行方法后,将数据存储中Redis的

具体的实现步骤

  1. AOP拦截注解过的方法后,先去校验中key是否存在,
  2. 存在的话,执行后续方法,判断方法返回的类型,来确定序列化以及反序列化的规则
  3. 将Redis的key组装起来
  4. 去redis中查询数据,判断数据有无
  5. 在没有数据的情况下,就执行方法,将数据得到后,将结果保存到redis中
  6. 在有数据的情况下,就直接返回Redis中数据即可。

具体实现

自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisSimpleCache {

    /**
     * 过期时间 单位为秒
     * @return
     */
    int expire() default 86400;

    /**
     * redis 的key
     * @return
     */
    String redisKey() default "";

    /**
     * 在请求后是否刷新过期时间  true为刷新 false 为不刷新
     * @return
     */
    boolean reCalFlag() default false;
}
/**只用来标记Key*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface RedisParamKey {

}

AOP实现

@Aspect
@Slf4j
@Component
public class SimpleCacheAopAdvice extends RedisBaseService {

    public static final String BLAKE = ":";

    public Random random;
    
    @Pointcut(value = "@annotation(com.xixi.myredis.tool.annotation.RedisSimpleCache)")
    public void test(){

    }

    public SimpleCacheAopAdvice() {
        this.random = new Random();
    }

    /**
     * 需要注意的问题 为了防止缓存雪崩,在设置过期时间的时候,要添加一个随机时间
     * 为了防止 两个请求参数相同,都没有在redis中找到数据,同时去执行查询,
     * 在查询之后,需要再次判断是否存在缓存 存在就不将数据加入缓存中
     * 后续需要考虑为了防止缓存穿透的问题 要进行一个setnx的 互斥锁
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around(value = "test()")
    public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
        //1. 先去校验注解 RedisKey 是否存在 是否只有一个
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        log.info("执行方法:{}",method.getName());
        List paramKey = getParamKey(method, joinPoint);
        if(paramKey.size()>1){
            log.error("{} 不支持多个RedisParamKey注解,将直接执行方法",method);
            return joinPoint.proceed();
        }
        RedisSimpleCache annotation = method.getAnnotation(RedisSimpleCache.class);
        int expire = annotation.expire();
        String redisKey = annotation.redisKey();
        boolean reCalFlag = annotation.reCalFlag();
        //2.得到返回类型 来确定 序列化的规则 对于 List 与Map 要进行特别的数据,
        Type genericReturnType = method.getGenericReturnType();
        boolean assignableFrom = genericReturnType.getClass().isAssignableFrom(Class.class);
        MyRedisSerializer myRedisSerializer = null;
        if(!assignableFrom){
            ResolvableType resolvableType = ResolvableType.forMethodReturnType(method);
            //可能为List 或者 map
            if(genericReturnType.getClass().isAssignableFrom(List.class)){
                Class<?> aClass = resolvableType.resolveGeneric(0);
                myRedisSerializer = new MyRedisSerializer(List.class);
                myRedisSerializer.setClassList(Lists.newArrayList(aClass));
            }else if(genericReturnType.getClass().isAssignableFrom(Map.class)){
                myRedisSerializer = new MyRedisSerializer(Map.class, CommonConstants.MAP);
                Class<?> keyClass = resolvableType.resolveGeneric(0);
                Class<?> valueClass = resolvableType.resolveGeneric(0);
                myRedisSerializer.setClassList(Lists.newArrayList(keyClass,valueClass));
            }else {
                myRedisSerializer = new MyRedisSerializer(resolvableType.resolve());

            }
        }else {
            String typeName = genericReturnType.getTypeName();
            myRedisSerializer = new MyRedisSerializer(Class.forName(typeName));
        }
        //3. 组装去redis 查询的key
        StringBuffer sb = new StringBuffer();
        sb.append(redisKey).append(BLAKE);
        if(!paramKey.isEmpty() && paramKey.get(0)!=null){
            sb.append(paramKey.get(0));
        }
        log.info("查询的key为 {}",sb.toString());

        try {
            return read(joinPoint,sb.toString(),expire+random.nextInt(1000),reCalFlag,myRedisSerializer);
        } catch (Exception e) {
            log.error("查询 key:{} ,出错了",sb.toString(),e.getMessage());
            return joinPoint.proceed();
        }

    }


    /**
     * 得到带有RedisParamKey 注解的参数
     * @param method
     * @param joinPoint
     * @return
     */
    private List getParamKey(Method method,ProceedingJoinPoint joinPoint) {
        List list = new ArrayList();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i=0; i<parameterAnnotations.length;i++){
            for (int j =0;j< parameterAnnotations[i].length;j++){
                if(parameterAnnotations[i][j] instanceof RedisParamKey){
                    Object[] args = joinPoint.getArgs();
                    list.add(args[i]);
                }
            }
        }
        return list;
    }
}

 /**
     * 去redis中查询数据,没有数据的话,将结果查询出来,再放入到redis中。
     * @description:
     * @author: shengchengchao
     * @date 2021/3/24
     * @return
     */
    public Object read(ProceedingJoinPoint joinPoint, String key, int expire, boolean reCalFlag, MyRedisSerializer myRedisSerializer) throws Throwable {
        //先去redis中去查询
        //使用Pipeline可以批量执行redis命令,防止多个命令建立多个连接
        byte[] serialKey = RedisSerialUtils.serial(key);
        List redisResult = redisTemplate.executePipelined((RedisCallback) conn -> {
            byte[] bytes = conn.get(serialKey);
            return null;
        }, null);
        Random random =new Random();
        byte[] bytes = (byte[]) redisResult.get(0);
        //没有缓存
        if(bytes ==null ){
            log.info("没有找到缓存,执行方法");
            Object resultSave = getResultSave(joinPoint, serialKey, expire, reCalFlag, myRedisSerializer);
            log.info("已得到结果并存入redis中");
            return resultSave;
        }else if(bytes.length==1 && bytes[0] ==0 ){
            //缓存为null
            return null;
        }else {
            //有缓存 将数据反序列化
            try {
                Object result = deserializeResult(myRedisSerializer, bytes);
                if(reCalFlag){
                    redisTemplate.expire(serialKey,expire+random.nextInt(100), TimeUnit.SECONDS);
                }
                return result;
            } catch (Exception e) {
                log.error("从redis中反序列化出错",e);
                return joinPoint.proceed();
            }
        }
    }

    /**
     *  执行数据 并将结果保存到redis中
     * @param joinPoint
     * @param key
     * @param expire
     * @param reCalFlag
     * @param myRedisSerializer
     * @return
     */
    private Object getResultSave(ProceedingJoinPoint joinPoint, byte[] serialKey, int expire, boolean reCalFlag, MyRedisSerializer myRedisSerializer) {
        try {
            Object proceed = joinPoint.proceed();
            if(proceed  !=null){
                //再次查找redis 确定结果
                List redisResult = redisTemplate.executePipelined((RedisCallback) conn -> {
                    byte[] bytes = conn.get(serialKey);
                    return null;
                }, null);
                byte[] bytes = (byte[]) redisResult.get(0);
                //找到结果的话 进行反序列化
                if(bytes !=null){
                    Object deserialize = deserializeResult(myRedisSerializer,bytes);
                    return deserialize;
                }else {
                    //将数据存储到redis中
                    saveRedis(proceed,myRedisSerializer,serialKey,expire);
                }
            }
            return proceed;
        } catch (Throwable throwable) {
            log.error("出现错误",throwable.getMessage());
        }
        return null;
    }

    /**
     * 保存数据到redis
     * @param result
     * @param myRedisSerializer
     */
    private void saveRedis(Object result, MyRedisSerializer myRedisSerializer,byte[] serialKey,int expire) {
        if(result!=null){
            //判断数据的类型 是否为null
            byte[] serialize = myRedisSerializer.serialize(result);
            redisTemplate.executePipelined((RedisCallback)con->{
                con.set(serialKey,serialize);
                if(expire!=CommonConstants.permanent){
                    con.expire(serialKey,expire);
                }
                return null;
            });

        }
    }

    /**
     * 反序列化 数据
     * @param myRedisSerializer 自定义的序列化
     * @param bytes 结果
     * @return
     */
    private Object deserializeResult(MyRedisSerializer myRedisSerializer,byte[] bytes){
        if(CommonConstants.MAP.equals(myRedisSerializer.getTypeName())){
            List<Class> classList = myRedisSerializer.getClassList();
            return MyFastJsonUtil.json2Map(new String(bytes), classList.get(0),classList.get(1));
        }else {
            return myRedisSerializer.deserialize(bytes);
        }

    }

具体使用

只需要在方法中添加上两个注解就可以完成,将数据存储到Redis,再次访问后,将先去查询数据库中数据。

@Service
public class TestService {

    @RedisSimpleCache(redisKey = "test",expire = 100)
    public String test(@RedisParamKey String k1 ){
        System.out.println("进入查询");
        return "aaa";
    }
}
无人机避障——Mid360+Fast-lio感知建图+Ego-planner运动规划(胎教级教程) 本工作将 Mid360 激光雷达、Fastlio-SLAM 和 Ego-Planner 集成,实现无人机的环境感知、定位建图和运动规划(仿真过程)—— 胎教级教程。 阅读详情

相关推荐

大学生智能车竞赛初次尝试经验分享--1(硬件绘制电路,pcb部分)

本人机械电子专业大二暑假参加全国大学生智能车竞赛,之前参加过一些其他类型的比赛有一点基础,由于之前疫情影响所以做智能车比赛断代了,没有学长带也没有思路,从一开始的一头雾水逐渐摸索出我感觉比较实用的经验。 一.首先是硬件的准备工作: 1.硬件部分与要自己设计需要的电路制成pcb。推荐使用Altium Designer20,AD比较常用所以有多实用的教学视频,另外需要自己了解布线规则,包括信号线,电源线常用的宽度 一些电源电路,驱动电路建议用multisim仿真,没有问题再进行制版 单片机有关的需要仿真

weixin_52679034的博客 9834

Redis的两个常用工具类

1.RedisConfig配置类 启用Spring对Redis缓存支持,把Redis放入到Spring容器中 package com.example.ctrip.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccesso...

Hexialin的博客 852

DrissionPage实战:5分钟搞定京东滑块验证码自动登录(附完整轨迹生成代码)

本文详细介绍了如何使用DrissionPage工具实现京东滑块验证码的自动化登录,包括环境配置、滑块缺口识别、拟人化轨迹生成算法及完整登录流程。通过物理运动学模型和智能识别技术,有效提升验证码破解成功率,适用于电商自动化场景。

weixin_29229055的博客 244

Java幂等性实现(Redis+AOP)

什么是幂等? 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的.更复杂的操作幂等保证是利用唯一交易号(流水号)实现。 ——来自百度百科 通俗的讲: 同一个系统,同一个接口方法,相同的参数执行多次,得到的结果都是一样的。 幂等解决的场景 如果某些场景接口方法重复

qq_42987270的博客 2769

基于立创EDA的IP5389BZ+IP6829 100W有线15W无线带屏充电宝DIY全流程解析

本文详细解析了基于立创EDA平台,采用IP5389BZ和IP6829芯片DIY一款100W有线快充、15W无线充电并带显示屏的移动电源的全过程。内容涵盖核心功能规划、硬件选型、电路设计、PCB布局、焊接组装、分步调试以及软件逻辑,为电子爱好者提供了一份从原理到实践的完整指南,并重点强调了电池安全与IP6829供电电压等关键注意事项。

weixin_34374684的博客 307

Redis高级用法--AOP实现

1.利用AOP实现Redis缓存

jerrywu2的博客 1266

手把手教你用YOLOv11-seg-LSCD搞定树脂件瑕疵检测(附数据集与代码)

本文详细介绍了如何使用YOLOv11-seg-LSCD模型进行工业级树脂件瑕疵检测,包括环境配置、数据集构建、模型训练与调优、部署优化等全流程。通过LSCD模块的创新设计,有效提升了气泡、裂纹等微小缺陷的检测精度,适用于各类树脂制品的质量检测场景。

weixin_28677005的博客 418

Aop实现注解限流和Redis缓存

JLLang https://my.oschina.net/jiansin 限流注解实现 业务系统中某些接口需要进行限流的时候在spring家族中可以采用RateLimiter进行接口限流,减轻服务器的压力。实现思路如下: RateLimit 注解 /** * @description: 限流注解 * @author: lilang * @version: * @modified By:...

JvLLang的博客 302

ZeroClaw安装、使用方法详细全解

ZeroClaw 是一个用 Rust 编写的轻量级、高性能、完全自主的 AI 助手运行时框架

洛星核的博客 618

《民机适航入门指南:从法规学习到项目实践的全面解析》

适航(Airworthiness)是指航空器、其发动机、螺旋桨和其他部件在规定条件下,能够安全飞行并满足有关航空法规规定的条件。简而言之,适航代表了航空器在任何时候都能安全运营的状态。适航在民用航空领域尤为关键,因为飞机必须经过严格的适航认证才能投入商用。这些认证的目标是确保飞机能够在广泛的运行条件下保持飞行安全,从而保护乘客、机组和地面人员的生命财产安全。适航法规覆盖了飞机的整个生命周期,包括设计、制造、测试、运营、维护和报废。

qq_41854911的博客 994

Redis持久化-AOP

AOP(Append only File) 将所有命令都记录下来,history,恢复的时候把这个文件全部执行一遍!

Hello World 1122

【全网首发】2026华为OD双机位C卷 机考真题题库含考点说明以及在线OJ(OD上机考试双机位C卷)

【全网首发】2026华为OD机位C卷 机考真题题库含考点说明以及在线OJ(OD上机考试双机位C卷)

算法大师! 8801

【aop+redis+spel表达式】注解实现分布式锁

采用spel+redis实现注解分布式锁

qq_42961251的博客 1553

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

基于Spring的Java银行存取系统

基于Spring的Java银行存取系统是一个模拟银行核心业务的信息化管理平台,旨在通过Java语言和Spring框架实现账户管理、存取款操作、交易记录查询、用户权限控制等核心功能。系统采用分层架构设计,结合Spring的依赖注入(DI)和面向切面编程(AOP)特性,提升代码的可维护性和可扩展性,同时通过数据库存储用户信息和交易数据,确保数据的完整性和安全性。

qq_q992250277的博客 1053

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

SpringBoot结合Redis 自定义数据缓存

缓存的主要目的是提升系统的性能、处理高并发情况,并减少对数据库的压力。通过将数据存储在内存中,当数据没有本质变化时,我们可以避免频繁地连接数据库进行查询。相反,我们可以首先尝试从缓存中读取数据,只有当缓存中未找到数据时,才会去数据库中查询。这样做可以显著减少数据库的读写操作,提高系统的性能和并发能力。接下来我将写一个简单的缓存小栗子。

Tzeao 1017

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

Redis概述

1. 概述

mjx715813的博客 152

SSM+redis集群整合

搭建完redis集群跟java结合起来(redis集群搭建见上一篇博客) 一:不使用框架的情况 1.引入jar(一个jedis jar就够了) 2.创建节点对象,把节点对象加入JedisCluster对象中,然后就可以正常使用 public static void main(String[] args) { // 创建并填充节点信息 Set<HostAndPort> nod...

qq_42011565的博客 1349

Mybatis结合Redis缓存以注解开发时的查询优化方案——重新实现@One、@Many注解

前言 该问题最早提出是在20年9月份开发新院统战项目时提出的,也就是我刚刚搭完第一版后端框架,其中持久层使用的框架是mybatis plus,缓存使用的是sprinboot缓存注解+redis,其中将缓存注解加在service层。 当时在使用mybatis plus并以注解开发时,由于过度使用@One与@Many注解进行属性的封装,例如: @Select("SELECT id,dept_id,username,nick_name,gender,phone,email,avatar_path,passwo

weixin_42822484的博客 814

redis缓存和SpringCache

Redis缓存代码实现,Redis缓存原理代码实现,SpringCache快速入门启动,Spring Cache使用,SpringCache结合Redis,项目中自定义缓存。

black_pp的博客 2434

SpringCache和redis区别?什么是SpringCache?

spring cache是什么

Hanson的博客 3367

RedisClient类中实现Redis的相关操作

Java操作Redis 2016年03月31日 12:49:24 lxhjh 阅读数:6591 目前Jedis是官方推荐的比较好的Redis操作API包,我们这里结合Spring来看下如何使用Redis这一神器。 1、在pom文件中引入jredis包: &lt;dependency&gt;   &lt;groupId&gt;redis.clients&lt;/groupId&gt;   &...

zxl2016的博客 5211

SpringBoot实战九:集成Redis与自定义序列化

本实验完成SpringBoot与Redis的集成,重点解决序列化乱码问题。通过自定义Jackson序列化器配置RedisTemplate,实现Java对象与JSON格式的高效转换。实验包含依赖引入、连接配置、序列化优化及功能测试,验证了字符串和对象在Redis中的正确存储与读取,为缓存开发奠定基础。

奔跑吧,兄弟 1004

Redis缓存操作记录

Redis相关配置: 1)第一步:在application.properties中配置缓存管理器: 其中: spring.cache.type 缓存类型 spring.cache.chache-names 缓存名称,多个名称可以使用逗号分隔 2)第二步 :加入驱动缓存的注解@EnableCaching 3)第三步:Redis配置 操作记录: 1.添加数据 @Ca...

chenkaibsw的专栏 1827

采用redis作为独立缓存服务器(三)_spring Cache后传之aop优化

请先阅读上一篇!!! aop的知识请参考我的博文【http://blog.youkuaiyun.com/wx5040257/article/details/78878645】 上一篇提到,spring cache要精确控制缓存,比方说删除一个用户数据不至于清空所有的用户缓存,可以办到,但是代码不优雅,需要项目组程序员要有较高的素养, 那么切面编程(aop)就可以解决这一困境!!! 废话不多少了,上代码

御前两把刀的专栏 434

Redis实例-2

在 startFormat 方法中,使用 Redis 的 stringRedisTemplate.opsForValue().set 方法设置任务状态为 PROCESSING,防止重复提交。:在任务启动前,通过 stringRedisTemplate.opsForValue().get(taskKey) 检查任务是否已在处理中。queueCapacity:任务队列容量,当核心线程都在忙碌时,新任务会进入队列等待。corePoolSize:核心线程数,表示线程池中始终保持的最小线程数量。

Karen博客 232
上一篇: redis Sentinel 部署以及spring boot集成
下一篇: Spring事务源码阅读(一)
枫辰215
博客等级 码龄9年 0粉丝 7原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值