乐趣区

关于redis:分布式电商项目六Redis缓存上

Redis 缓存学习

优化现有架构

阐明: 通过缓存服务器能够无效的晋升用户的拜访的效率.
注意事项:
1. 缓存的数据结构 应该选用 K- V 构造 只有 key 惟一 那么后果必然雷同…
2. 缓存中的数据不可能始终存储, 须要定期将内存数据进行优化 LRU 算法…
3. 缓存要求运行速度很快, C 语言实现… 运行在内存中.
4. 如果缓存运行的数据在内存中, 如果断电 / 宕机, 则内存数据间接失落. 实现内存数据的 长久化操作(磁盘).

Redis 缓存服务器

网址: http://www.redis.cn/

Redis 介绍

Redis 是一个开源(BSD 许可)的,内存中的数据结构存储系统,它能够用作数据库、缓存和消息中间件 。它反对多种类型的数据结构,如 字符串(strings),散列(hashes),列表(lists),汇合(sets),有序汇合(sorted sets) 与范畴查问,bitmaps,hyperloglogs 和 天文空间(geospatial)索引半径查问。Redis 内置了 复制(replication),LUA 脚本(Lua scripting),LRU 驱动事件(LRU eviction),事务(transactions)和不同级别的 磁盘长久化(persistence),并通过 Redis 哨兵(Sentinel)和主动 分区(Cluster)提供高可用性(high availability)。
nginx: 3- 5 万 / 秒
redis: 读: 11.2 万 / 秒 写: 8.6 万 / 秒 均匀 10 万 / 秒
吞吐量: 50 万 / 秒

Redis 装置

1). 解压 redis 文件

2). 挪动文件 / 批改文件

3). 装置 Redis
命令 1: make
–C 语言编写的须要编译成二进制文件运行
命令 2: make install
– 编译实现后装置

批改 redis.conf 配置文件(通过辅助软件 MobaXterm)

1. 批改 IP 绑定

2. 敞开保护模式

3. 开启后盾启动

批改 Redis 的配置文件(通过命令模式)

命令 1: 展示行号 :set nu
![Image \[1\].png](/img/bVcIDmr)
批改地位 1: 正文 IP 绑定
![Image \[2\].png](/img/bVcIDmq)
批改地位 2: 敞开保护模式
![Image \[3\].png](/img/bVcHYr7)
批改地位 3: 开启后盾启动
![Image \[4\].png](/img/bVcHYse)

Redis 命令

1. 启动 redis redis-server redis.conf须要用到配置信息的启动形式(redis—server 启动示意恪守默认配置启动,则只能在本机应用不能外联)

2. 查看 redis 服务项

3. 进入 redis 客户端

redis-cli  -p 6379

4. 敞开 redis
1). 命令 redis-cli -p 6379 shutdown如果是默认的 6379 端口号
2).kill 命令 kill -9 pid 号

SpringBoot 整合 Redis

导入 jar 包

        <!--spring 整合 redis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </dependency>

入门案例

package com.jt;

import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.params.SetParams;

import java.util.List;
import java.util.Map;
import java.util.Set;

public class TestRedis {

    /**
    * 1. 实现 redis 测试
    * 报错查看:* 1. 查看 redis.conf 配置文件(1).ip 绑定问题(2). 保护模式问题(3). 后盾启动问题
    * 2. 启动 redis 启动形式 redis-server redis.conf
    * 3. 查看防火墙
    */

    @Test
    public void test01(){Jedis jedis=new Jedis("192.168.126.129", 6379);
        jedis.set("2007", "redis 入门案例");
        System.out.println(jedis.get("2007"));
    }

    /**
    * 判断是否有 key 数据,如果没有则新增数据,如果有则放弃新增
    */

    @Test
    public void test02(){Jedis jedis=new Jedis("192.168.126.129", 6379);
        // if(!jedis.exists("2007")){ // 判断数据是否存在
        // jedis.set("2007", "测试案例");
        // }
        // System.out.println(jedis.get("2007"));

        //setnx 作用:如果有数据,则不做解决;
        jedis.setnx("2007", "测试高级用法");
        System.out.println(jedis.get("2007"));
    }

    /**
    * 需要:* 向 redis 中增加一个数据库.set-key-value,要求增加超时工夫 100 秒
    * 暗藏 bug:*(死锁操作的一种体现)代码执行过程中,如果报错,则可能删除失败
    * 原子性:要么同时胜利,要么同时失败
    * 解决办法:将入库操作和超时工夫一起设定
    */
    @Test
    public void test03() throws InterruptedException {Jedis jedis=new Jedis("192.168.126.129", 6379);
        // 有隐患 bug
        //jedis.set("2007", "测试工夫");
        //int a = 1/0;// 假如一个异样
        // 暗藏含意:业务须要 到期删除数据
        //jedis.expire("2007", 100);

        // 解决办法 新的 API 同时赋值与设置工夫
        jedis.setex("2007", 100, "测试工夫");

        Thread.sleep(3000);
        System.out.println(jedis.ttl("2007")+"秒");
    }

    /**
    * 1. 如果数据存在则不操作数据 setnx
    * 2. 同时设定超时工夫,留神原子性 setex
    * setParams 参数阐明:* XX = "xx"; 只有 key 存在,则进行操作
    * NX = "nx"; 没有 key 存在,则进行操作
    * PX = "px"; 指定 key 失效工夫,单位:毫秒
    * EX = "ex"; 指定 key 失效工夫,单位:秒
    */
    @Test
    public void test04() throws InterruptedException {Jedis jedis=new Jedis("192.168.126.129", 6379);
        SetParams setParams = new SetParams();
        setParams.nx().ex(100);
        jedis.set("2007", "aaa",setParams);

    }

    @Test
    public void testHash() throws InterruptedException {Jedis jedis=new Jedis("192.168.126.129", 6379);
        jedis.hset("person", "id","18");
        jedis.hset("person", "name","hash 测试");
        jedis.hset("person", "age","20");
        jedis.hset("person", "sex","男");

        Map<String, String> map = jedis.hgetAll("person");
        Set<String> set = jedis.hkeys("person"); // 获取所有的 key
        List<String> list = jedis.hvals("person"); // 获取所有的 val
    }

    @Test
    public void testList() throws InterruptedException {Jedis jedis=new Jedis("192.168.126.129", 6379);
        jedis.lpush("list", "1,2,3,4,5");
        System.out.println(jedis.rpop("list")); //1,2,3,4,5
        jedis.lpush("list", "1","2","3","4","5");
        System.out.println(jedis.rpop("list")); //1
        jedis.flushDB();}

    @Test
    public void testTx() throws InterruptedException {Jedis jedis=new Jedis("192.168.126.129", 6379);
        //1. 开启事务
        Transaction transaction = jedis.multi();
        try {transaction.set("a", "a");
        transaction.set("b", "b");
        transaction.set("c", "c");
        transaction.exec(); // 提交事务}catch (Exception e){transaction.discard();
        }
    }
}

Redis 命令

在我的项目中编写测试类,连贯 redis

package com.jt;

import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.params.SetParams;

import java.util.List;
import java.util.Map;
import java.util.Set;

public class TestRedis {

    /**
    * 1. 实现 redis 测试
    * 报错查看:* 1. 查看 redis.conf 配置文件(1).ip 绑定问题(2). 保护模式问题(3). 后盾启动问题
    * 2. 启动 redis 启动形式 redis-server redis.conf
    * 3. 查看防火墙
    */

    @Test
    public void test01(){Jedis jedis=new Jedis("192.168.126.129", 6379);
        jedis.set("2007", "redis 入门案例");
        System.out.println(jedis.get("2007"));
    }

    /**
    * 判断是否有 key 数据,如果没有则新增数据,如果有则放弃新增
    */

    @Test
    public void test02(){Jedis jedis=new Jedis("192.168.126.129", 6379);
        // if(!jedis.exists("2007")){ // 判断数据是否存在
        // jedis.set("2007", "测试案例");
        // }
        // System.out.println(jedis.get("2007"));

        //setnx 作用:如果有数据,则不做解决;
        jedis.setnx("2007", "测试高级用法");
        System.out.println(jedis.get("2007"));
    }

    /**
    * 需要:* 向 redis 中增加一个数据库.set-key-value,要求增加超时工夫 100 秒
    * 暗藏 bug:*(死锁操作的一种体现)代码执行过程中,如果报错,则可能删除失败
    * 原子性:要么同时胜利,要么同时失败
    * 解决办法:将入库操作和超时工夫一起设定
    */
    @Test
    public void test03() throws InterruptedException {Jedis jedis=new Jedis("192.168.126.129", 6379);
        // 有隐患 bug
        //jedis.set("2007", "测试工夫");
        //int a = 1/0;// 假如一个异样
        // 暗藏含意:业务须要 到期删除数据
        //jedis.expire("2007", 100);

        // 解决办法 新的 API 同时赋值与设置工夫
        jedis.setex("2007", 100, "测试工夫");

        Thread.sleep(3000);
        System.out.println(jedis.ttl("2007")+"秒");
    }

    /**
    * 1. 如果数据存在则不操作数据 setnx
    * 2. 同时设定超时工夫,留神原子性 setex
    * setParams 参数阐明:* XX = "xx"; 只有 key 存在,则进行操作
    * NX = "nx"; 没有 key 存在,则进行操作
    * PX = "px"; 指定 key 失效工夫,单位:毫秒
    * EX = "ex"; 指定 key 失效工夫,单位:秒
    */
    @Test
    public void test04() throws InterruptedException {Jedis jedis=new Jedis("192.168.126.129", 6379);
        SetParams setParams = new SetParams();
        setParams.nx().ex(100);
        jedis.set("2007", "aaa",setParams);

    }

    @Test
    public void testHash() throws InterruptedException {Jedis jedis=new Jedis("192.168.126.129", 6379);
        jedis.hset("person", "id","18");
        jedis.hset("person", "name","hash 测试");
        jedis.hset("person", "age","20");
        jedis.hset("person", "sex","男");

        Map<String, String> map = jedis.hgetAll("person");
        Set<String> set = jedis.hkeys("person"); // 获取所有的 key
        List<String> list = jedis.hvals("person"); // 获取所有的 val
    }

    @Test
    public void testList() throws InterruptedException {Jedis jedis=new Jedis("192.168.126.129", 6379);
        jedis.lpush("list", "1,2,3,4,5");
        System.out.println(jedis.rpop("list")); //1,2,3,4,5
        jedis.lpush("list", "1","2","3","4","5");
        System.out.println(jedis.rpop("list")); //1
        jedis.flushDB();}

    @Test
    public void testTx() throws InterruptedException {Jedis jedis=new Jedis("192.168.126.129", 6379);
        //1. 开启事务
        Transaction transaction = jedis.multi();
        try {transaction.set("a", "a");
            transaction.set("b", "b");
            transaction.set("c", "c");
            transaction.exec(); // 提交事务}catch (Exception e){transaction.discard();
        }
    }
}

SpringBoot 整合 Redis

编辑 pro 配置文件

因为 redis 的 IP 地址和端口都是动态变化的, 所以通过配置文件标识数据. 因为 redis 是公共局部, 所以写到 common 中.

编辑配置类

package com.jt.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import redis.clients.jedis.Jedis;

@Configuration // 标识我是配置类
@PropertySource("classpath:/properties/redis.properties")
public class RedisConfig {@Value("${redis.host}")
    private String host;
    @Value("${redis.port}")
    private Integer port;

    @Bean
    public Jedis jedis(){return new Jedis(host, port);
    }
}

对象与 JSON 串转化

对象转化 JSON 入门案例

关键点:
(1)初始化 ObjectMapper 对象
(2)转换 JSON 调用 writeValueAsString(对象)
(3)转换成对象调用 readValue(json 串,对象类型)

package com.jt;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jt.pojo.ItemDesc;
import com.jt.util.ObjectMapperUtil;
import org.junit.jupiter.api.Test;

import java.util.Date;

public class TestObjectMapper {private static final ObjectMapper MAPPER=new ObjectMapper();

    /**
    * 1. 对象如何转化为 JSON 串的?* 步骤:(1)获取对象的所有 getXXX()办法
    *(2)将获取的 getXXX 办法的前缀 get 去掉造成了 json 的 key=xxx
    *(3)通过 getXXX 办法的调用获取属性的值,造成了 json 的 value 的值
    *(4)将获取到的数据,利用 json 格局进行拼接 {key:value,key2:value2...}
    * 2.JSON 串如何转化为对象的?* 步骤:(1)依据 class 参数的类型,利用 java 的反射机制,实例化对象
    *(2)解析 JSON 格局,辨别 key:value
    *(3)进行办法的拼接 setkey()名称
    *(4)调用对象的 setkey(value)
    *(5)最终将所有的 json 串中的 key 转化为对象的属性
    * @throws JsonProcessingException
    */
    @Test
    public void testToJson() throws JsonProcessingException {ItemDesc itemDesc=new ItemDesc();
        itemDesc.setItemId(100L)
        .setItemDesc("测试数据的转化")
        .setCreated(new Date())
        .setUpdated(new Date());
        //1. 将对象转化为 JSON
        String json = MAPPER.writeValueAsString(itemDesc);
        System.out.println(json);
        //2. 将 JSON 转换为对象 src: 须要转化的 JSON 串,valueType: 须要转化为什么对象格局
        ItemDesc itemDesc2 = MAPPER.readValue(json, ItemDesc.class);
        /**
        * 字符串转化对象的原理
        */
        System.out.println(itemDesc2);//itemDesc2.toString()默认调用的是子类的,所以没有新增工夫和更新工夫,咱们用的 lombok}
}

编辑 ObjectMapper 工具 API

难点:
不将异样抛给调用者,间接 try/catch 解决
转换成对象时,如果咱们返回 Object 对象,对象须要强转,为了不便调用,能够依据传参中的对象类型来返回,用到泛型。<T> T 来示意返回的数据类型为传递来的参数的类型

package com.jt.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class ObjectMapperUtil {private static final ObjectMapper MAPPER=new ObjectMapper();

    // 将对象转化为 JSON
    public static String toJSON(Object target){
        try {return MAPPER.writeValueAsString(target);
        } catch (JsonProcessingException e) {e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    // 将 JSON 转化为对象
    public static <T> T toObject(String json,Class<T> targetClass){
        try {return MAPPER.readValue(json, targetClass);
        } catch (JsonProcessingException e) {e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

实现商品分类缓存实现

编辑 ItemCatController

阐明: 切换业务调用办法, 执行缓存调用

/**
* 业务: 实现商品分类的查问
* url 地址: /item/cat/list
* 参数: id: 默认应该 0 否则就是用户的 ID
* 返回值后果: List<EasyUITree>
*/
@RequestMapping("/list")
public List<EasyUITree> findItemCatList(Long id){Long parentId=(id==null)?0:id;
    //return itemCatService.findItemCatList(parentId)
    return itemCatService.findItemCatCache(parentId);
}

编辑 ItemCatService

public interface ItemCatService {String findItemCatName(Long itemCatId);
    List<EasyUITree> findItemCatList(Long parentId);

    //redis 缓存读取
    List<EasyUITree> findItemCatCache(Long parentId);
}

编辑 ItemCatServiceImpl

 @Autowired(required = false) // 在程序启动时,如果没有该对象,就临时不加载
    private Jedis jedis;

/**
* Redis:
* 两大因素:key:业务标识 + :: + 变动的参数 ITEMCAT::0
* value:String 数据的 JSON 串
* 实现的步骤:* 先查问 redis 缓存:* 有:获取缓存数据之后转换为具体对象返回
* 没有:应该查询数据库,并将查问的后果转换为 JSON 之后保留到 redis 不便下次查问
*/
@Override
public List<EasyUITree> findItemCatCache(Long parentId) {Long startTime =System.currentTimeMillis();
    List<EasyUITree> treeList= new ArrayList<>();
    String key = "ITEMCAT_PARENTID::"+parentId;
    if (jedis.exists(key)){
        //redis 中有数据
        String json = jedis.get(key);
        // 将 json 转换为对象
        treeList = ObjectMapperUtil.toObject(json, treeList.getClass());
        Long endTime =System.currentTimeMillis();
        System.out.println("查问 redis 缓存的工夫"+(endTime-startTime)+"毫秒");
    }else {
        //redis 中没有数据,应该查询数据库
        treeList = findItemCatList(parentId);
        // 将对象那个转化为 JSON 串
        String json= ObjectMapperUtil.toJSON(treeList);
        // 将数据存储到 redis 缓存中
        jedis.set(key, json);
        Long endTime =System.currentTimeMillis();
        System.out.println("查询数据库的工夫"+(endTime-startTime)+"毫秒");
    }
    return treeList;
}

AOP 实现 Redis 缓存

现有代码存在的问题

1. 如果间接将缓存业务, 写到业务层中, 如果未来的缓存代码发生变化, 则代码耦合高, 必然重写编辑代码.
2. 如果其余的业务也须要缓存, 则代码的反复率高, 开发效率低.
解决方案: 采纳 AOP 形式实现缓存.

AOP

在软件业,AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译形式和运行期间动静代理实现程序性能的对立保护的一种技术。AOP 是 OOP 的连续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 能够对业务逻辑的各个局部进行隔离,从而使得业务逻辑各局部之间的耦合度升高,进步程序的可重用性,同时进步了开发的效率。

AOP 实现步骤

公式: AOP(切面) = 告诉办法(5 种) + 切入点表达式(4 种)

告诉温习

1.before 告诉 在执行指标办法之前执行
2.afterReturning 告诉 在指标办法执行之后执行
3.afterThrowing 告诉 在指标办法执行之后报错时执行
4.after 告诉 无论什么时候程序执行实现都要执行的告诉
上述的 4 大告诉类型, 不能控制目标办法是否执行. 个别用来记录程序的执行的状态.
个别利用与监控的操作.
5.around 告诉 (性能最为弱小的) 在指标办法执行前后执行.
因为盘绕告诉能够控制目标办法是否执行. 控制程序的执行的轨迹.

切入点表达式

1.bean(“bean 的 ID”) 粒度: 粗粒度 按 bean 匹配 以后 bean 中的办法都会执行告诉.
2.within(“包名. 类名”) 粒度: 粗粒度 能够匹配多个类
3.execution(“返回值类型 包名. 类名. 办法名(参数列表)”) 粒度: 细粒度 办法参数级别
4.@annotation(“包名. 类名”) 粒度: 细粒度 依照注解匹配

AOP 入门案例

package com.jt.aop;

import com.jt.anno.CacheFind;
import com.jt.util.ObjectMapperUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ShardedJedis;

import java.util.Arrays;
@Aspect // 标识我是一个切面
@Component // 交给 spring 容器治理
public class CacheAOP {

    // 切面 = 切入点表达式 + 告诉办法
    //@Pointcut("bean(itemCatServiceImpl)")
    //@Pointcut("within(com.jt.service.ItemCatServiceImpl)")
    //@Pointcut("within(com.jt.service.*)") //(.*)一级包门路 (..*)所有子孙后代包
    //@Pointcut("execution(返回值类型 包名. 类名. 办法名(参数列表))")
    @Pointcut("execution(* com.jt.service..*.*(..))") // 任意返回类型 在 com.jt.service 包下的所有子孙类 以 add 结尾的所有任意参数类型的办法
    public void pointCut(){}

    /**
    * 需要:* 1. 获取以后指标办法的门路
    * 2. 获取指标办法的参数
    * 3. 获取指标办法的名称
    */
    @Before("pointCut()")
    public void before(JoinPoint joinPoint){String classNamePath = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("办法门路 =" + classNamePath);
        System.out.println("办法名称 =" + methodName);
        System.out.println("办法参数 =" + Arrays.toString(args));
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint){
        try {System.out.println("盘绕告诉开始");
            Object obj= joinPoint.proceed();// 如果有下一个告诉,就执行下一个告诉,如果没有就执行指标办法
            System.out.println("盘绕告诉完结");
            return obj;
        } catch (Throwable throwable) {throwable.printStackTrace();
            throw new RuntimeException(throwable);
        }

    }
}

AOP 实现 Redis 缓存

业务实现策略

1). 须要自定义注解 CacheFind
2). 设定注解的参数 key 的前缀, 数据的超时工夫.
3). 在办法中标识注解.
4). 利用 AOP 拦挡指定的注解.
5). 应该应用 Around 告诉实现缓存业务.

编辑自定义注解

package com.jt.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD) // 注解对办法无效
@Retention(RetentionPolicy.RUNTIME) // 运行时无效
public @interface CacheFind {public String preKey(); // 定义 key 的前缀
    public int seconds() default 0; // 定义数据的超时工夫}

实现类的办法中标识注解

@CacheFind(preKey="ITEMCAT_PARENTID",seconds=0)// 设置 key 前缀和存活工夫
@Override
public List<EasyUITree> findItemCatList(Long parentId) {

编辑 CacheAOP

难点 1:

本来通过 @annotion(注解的全门路)这种方法想要获取注解的参数 preKey()须要特地繁琐的绿色被正文掉的的六步

当初扭转 @annotion(注解对象),加载该切面类时,会主动匹配注解对象的类型,从参数列表中获取,拦挡该类型的注解对象后作为参数传递给该办法,之后能够间接获取该注解对象的参数 perKey(),就能够省去简单的过程

难点 2:

在 json 转对象时须要获取拦挡的办法上的返回值类型,通过查找 Signature 接口。发现他有一个实现类为 MethodSignature 中有 getReturnType()办法,此时咱们须要将 Signature 强转向下转型,能力调用该办法

package com.jt.aop;

import com.jt.anno.CacheFind;
import com.jt.util.ObjectMapperUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ShardedJedis;

import java.util.Arrays;


@Aspect // 标识我是一个切面
@Component // 交给 spring 容器治理
public class CacheAOP {

    @Autowired
    private ShardedJedis jedis;// 切换成分片 redis
    //private Jedis jedis;

    /**
    * 需要:* 1. 筹备 key= 注解的前缀 + 用户参数
    * 2. 从 redis 中获取数据
    * 有:从缓存中获取数据之后,间接返回值
    * 没有:查询数据库之后再次保留到缓存中即可
    * 办法:* 动静获取注解的类型,看上去是注解的名称,然而本质是注解的类型,只有切入点的满足条件
    * 则会传递注解对象类型。* @param joinPoint 该参数必须位于参数列表的第一位,否则会报参数异样
    * @return
    * @throws Throwable
    */

    @Around("@annotation(cacheFind)")
    public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind) throws Throwable {
        Object result = null; // 定义返回值对象
        String preKey = cacheFind.preKey();
        String key =preKey+"::"+Arrays.toString(joinPoint.getArgs());
        //1. 校验 redis 中是否有数据
        if(jedis.exists(key)){
            //1. 如果数据存在,须要从 redis 中获取 json 数据,之后间接返回
            String json =jedis.get(key);
            // 获取办法对象 获取返回值类型
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Class returnType = methodSignature.getReturnType();
            //json 转对象
            result = ObjectMapperUtil.toObject(json, returnType);
            System.out.println("读缓存");
            }else{
            // 代表没有数据,须要查询数据库
            result = joinPoint.proceed();
            // 将数据转换为 JSON
            String json = ObjectMapperUtil.toJSON(result);
            // 判断是否设置超时工夫
            if(cacheFind.seconds()>0){jedis.setex(key, cacheFind.seconds(), json);
        }else {jedis.set(key,json);
        }
        System.out.println("读库");
    }

    return result;
}




/*  @Around("@annotation(com.jt.anno.CacheFind)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //1. 获取指标对象的 class 类型
        Class<?> targetClass = joinPoint.getTarget().getClass();
        //2. 获取指标办法名称
        String methodName = joinPoint.getSignature().getName();
        //3. 获取参数类型
        Object[] args = joinPoint.getArgs();
        Class[] argsClass= null;
        //4. 对象那个转化为 class 类型
        if (args.length>0){argsClass= new Class[args.length];
            for(int i = 0 ; i <args.length; i++){argsClass[i] =args[i].getClass();}
        }
        //5. 获取办法对象
        Method targetMethod = targetClass.getMethod(methodName, argsClass);
        //6. 获取办法上的注解拼接成残缺 key
        if (targetMethod.isAnnotationPresent(CacheFind.class)){CacheFind cacheFind=targetMethod.getAnnotation(CacheFind.class);
            String key = cacheFind.preKey()+"::"+ Arrays.toString(joinPoint.getArgs());
    }
    return null;
}*/
退出移动版