springboot2 整合 redis 并通过 aop 实现自定义注解

1,相关依赖

pom.xml 片段


    org.springframework.boot
    spring-boot-starter-aop

    org.springframework.boot
    spring-boot-starter-data-redis

    org.apache.commons
    commons-pool2

2,配置文件

application.yml 片段,可以根据自己的需要自行修改

spring.redis.host:                    Redis 服务地址
spring.redis.port:                    Redis 服务端口
spring.redis.password:                Redis 访问的密码
spring.redis.database:                Redis 被连接的库号数
spring.redis.lettuce.pool.max-active: 连接池 最大线程数
spring.redis.lettuce.pool.max-wait:   连接池 最大阻塞等待时间 -1ms 为一直等待
spring.redis.lettuce.pool.max-idle:   连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.min-idle:   连接池中的最小空闲连接 默认 0
spring:
  redis:
    host: 192.168.200.100
    port: 6379
    password: 920619
    database: 0
    lettuce:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0

3,简单的使用

// 声明泛型的时候自动注入 @Autowired 会失效,这里使用 @Resource
// 该类操作任意类型的数据,但是存入到 redis 的数据为二进制,难以查看
@Resource
private RedisTemplate redisTemplate;

// 该类只能操作字符类型的数据,存入到 Redis 的为字符串,查看比较友好
@Autowired
private StringRedisTemplate redisTemplate;
redisTemplate.opsForValue().get(key);
redisTemplate.opsForValue().set(key, value, cacheRedis.deadline(), TimeUnit.SECONDS);

4,官方提供的注解

通过 get 和 set 的方式读写,会对代码造成一定的侵入性,对于全局性的修改不友好,而对于只需要在方法上加一个注解,就能实现自动读写的操作,简直不要太好

@Cacheable @CachePut @CacheEvict

这几个注解已经基本可以解决我们 吧 redis 作为缓存的需要,也很友好,具体可以参考:

可惜的是,官方提供的注解并不支持个性化过期时间的配置,我们只能在全局配置统一过期时间,这显然不够友好

5,自定义注解

为了解决过期时间配置的问题,我们可以通过自定义注解 AOP 面向切面的 的方式实现

package com.hwq.data.base.annotate;

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 CacheRedis {

    String prefix();               // Redis 健值的前缀
    String[] element() default {}; // 参与生成健值的元素,与方法上的参数签名保持一致
    long deadline() default 30L;   // 过期时间(秒),设置为 -1 永不过期

}

主要思路就是,通过注解和挂有该注解方法的参数签名和值生成 redis 健,在通过 aop 的环绕通知,拦截方法先查询 redis 健在 redis中是否存在,存在直接返回 值,不存在执行方法,并把返回值存入 redis

package com.hwq.data.base.aop;

import com.hwq.common.exception.ServerException;
import com.hwq.data.base.annotate.CacheRedis;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.CodeSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
@Aspect
public class CacheRedisAop {

    /**
     * 声明泛型的时候自动注入 @Autowired 会失效,这里使用 @Resource 注解
     * 这种可以操作任意类型的数据,但是存入 redis 的数据为二进制,难以直接查看
     */
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 环绕通知,拦截 redis 的写入和读取,如果方法执行结果为 null 不进行保存
     * @param point      切点对象
     * @param cacheRedis 注解内容
     */
    @Around(value = "@annotation(cacheRedis)", argNames = "point,cacheRedis")
    public Object aop(ProceedingJoinPoint point, CacheRedis cacheRedis) throws Throwable {
        Map param = this.mapParam(point);
        String key = this.buildKey(cacheRedis, param);
        Object value = redisTemplate.opsForValue().get(key);
        if (value == null) {
            value = point.proceed();
            if (value != null) {
                if (cacheRedis.deadline() == -1L) {
                    redisTemplate.opsForValue().set(key, value);
                } else {
                    redisTemplate.opsForValue().set(key, value, cacheRedis.deadline(), TimeUnit.SECONDS);
                }
            }
        }
        return value;
    }

    /**
     * 获取参数签名 和 参数值,并封装为 MAP 集合
     * @param point 切点
     */
    private Map mapParam(ProceedingJoinPoint point) {
        Map map = new HashMap<>();
        String[] names = ((CodeSignature) point.getSignature()).getParameterNames();
        Object[] values = point.getArgs();
        for (int i = 0; i < names.length; i++) {
            map.put(names[i], values[i]);
        }
        return map;
    }

    /**
     * 拼装 Redis 的健,具体规则:前缀 + :参数 + :参数 ......

     * 对于值为空,或者值为无效字符串的,忽略
     * 目前只支持 参数类型 为基础类型或字符串,如果有兴趣可以自行扩展该方法,比如通过反射机制实现支持类里面的元素
     */
    private String buildKey(CacheRedis cacheRedis, Map param) {
        ServerException.judge(StringUtils.isBlank(cacheRedis.prefix()), "注解 CacheRedis 的 prefix 不能为无效字符串");
        StringBuilder key = new StringBuilder(32);
        key.append(cacheRedis.prefix());
        for (int i = 0; i < cacheRedis.element().length; i++) {
            String name = cacheRedis.element()[i];
            Object value = param.get(name);
            if (value == null) {
                continue;
            }
            String str = value.toString();
            if (StringUtils.isBlank(str)) {
                continue;
            }
            key.append(":").append(str);
        }
        return key.toString();
    }
}

接下来我们就可以愉快的使用了,我们只需要在方法上声明注解,填入前缀,参与生成 redis 健的参数,过期时间,就能实现 redis 的自动读写了,笔者这边封装的比较简单,实际使用时可以根据具体业务自行扩展,大体思路也就这样

package com.hwq.data.base.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hwq.data.base.annotate.CacheRedis;
import com.hwq.data.base.entity.User;
import com.hwq.data.base.mapper.UserMapper;
import org.springframework.stereotype.Service;

@Service
public class UserService extends BaseService {

    /**
     * 条件查询获取用户
     * @param name 用户昵称
     */
    @CacheRedis(prefix = "user", element = {"name"}, deadline = 120)
    public User getByName(String name) {
        LambdaQueryWrapper wrapper = new QueryWrapper().lambda();
        wrapper.eq(User::getName, name);
        return getOne(wrapper);
    }
}

Original: https://www.cnblogs.com/lovling/p/14543492.html
Author: 被遗忘的优雅
Title: springboot2 整合 redis 并通过 aop 实现自定义注解

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/529300/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Linux下的SELINUX

    理解Linux下的SELinux 长久以来,每当遇到授权问题或者新安装的主机,我的第一反应是通过 setenforce 0命令禁用SELinux,来减少产生的权限问题,但是这并不是…

    Linux 2023年6月7日
    094
  • k8s集群中网络实现通信原理

    1)安装Docker时,创建一个名为 docke0 的虚拟网桥,虚拟网桥使用”10.0.0.0 -10.255.255.255 “、”172.1…

    Linux 2023年6月14日
    0107
  • 记一次大部分网络服务无法连接问题 (windows更新, steam登录, …)

    问题很简单,就是windows更新等了老半天都没反应,最后失败了。steam大多数情况是无法登陆… 最后搞了老半天,突然想到是不是DNS问题… 结果还真是 …

    Linux 2023年6月7日
    0110
  • 【网络安全篇】常见的HTTP状态码小结(3位5类)

    HTTP 状态码(HTTP Status Code)用以表示网页服务器传输协议的响应状态;状态码为 三位数,响应分为 五种;状态码的第1位数字表示状态类型,第2、3位数字表示具体的…

    Linux 2023年6月13日
    094
  • Linux编译安装、压缩打包与定时任务服务

    一、编译安装 即使用源代码编译安装的方式,编译打包软件。特点: 可以自定制软件; 可以按需构建软件; 编译安装案例 1、下载源代码包(这里以Nginx软件包源代码为例) wget …

    Linux 2023年5月27日
    0100
  • 学习一下 JVM (二) — 学习一下 JVM 中对象、String 相关知识

    一、JDK 8 版本下 JVM 对象的分配、布局、访问(简单了解下) 1、对象的创建过程 (1)前言Java 是一门面向对象的编程语言,程序运行过程中在任意时刻都可能有对象被创建。…

    Linux 2023年6月11日
    0118
  • 深入Go Map的使用技巧

    原文链接:https://www.zhoubotong.site/post/60.html之前写过一篇文章,Go map定义的几种方式以及修改技巧,今天发现还可以深入探讨下开发中容…

    Linux 2023年6月6日
    0130
  • K8S的apiVersion版本详解

    1. 背景 Kubernetes的官方文档中并没有对apiVersion的详细解释,而且因为K8S本身版本也在快速迭代,有些资源在低版本还在beta阶段,到了高版本就变成了stab…

    Linux 2023年6月14日
    088
  • CentOS7.6下Oracle19C RAC集群詳細搭建步驟

    CentOS7.6搭建RAC 1.系统环境配置 1.1概述 ​ 搭建两个节点的rac集群,其每个节点均有两个网卡,public网卡和private网卡。两个节点的主机名分别为rac…

    Linux 2023年6月13日
    079
  • docker容器编排原来这么丝滑~

    前言: 请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 概念介绍: Docker 这个东西所扮演的角色,容易理解,它是一个容器引擎,也就是说实际上我们的容器最终是由Doc…

    Linux 2023年6月14日
    0106
  • 防数据泄露_MySQL库和数据安全

    攻击场景 外部入侵 内部盗取 防御体系建设 参考 在企业安全建设中有一个方向是防数据泄露,其中一块工作就是保障数据库安全,毕竟这里是数据的源头。当然数据库也分不同的种类,不同类型的…

    Linux 2023年6月6日
    0123
  • Lvs

    Lvs Lvs Lvs简介 体系结构 LVS管理工具 配置 lvs-nat 模式的 httpd 负载集群—http 配置lvs-nat模式的httpd负载集群&#821…

    Linux 2023年6月6日
    0129
  • 微服务与领域驱动设计,架构实践总结

    怎样的架构才能配得上造到飞起的变化? 一、软件复杂性 1、复杂原因 如果软件系统存在持续的迭代周期,那么其中业务、技术、架构的复杂性都会直线拉升,其相应的开发难度也会提高,可以用一…

    Linux 2023年6月14日
    069
  • 【计算题】考研数据结构计算题型整理

    题型1:递归程序,一般使用公式进行递推 int fact(int n){ if(n 本题是求阶乘的递归代码,即n * (n-1) * …. * 1。每次递归调用 fac…

    Linux 2023年6月13日
    0119
  • linux mv 命令中断的解决办法

    背景:使用mv对服务器文件进行迁移的过程中,因为网络中断,所以迁移到一半被停止了。 现状:有一部分文件没有迁移完,但是文件夹已经存在了,重新mv提示文件夹已经存在,切已经有文件,所…

    Linux 2023年5月27日
    0117
  • 学习一下 SpringCloud (四)– 服务降级、熔断 Hystrix、Sentinel

    (1) 相关博文地址: 学习一下 SpringCloud (一)– 从单体架构到微服务架构、代码拆分(maven 聚合): https://www.cnblogs.com/l-y…

    Linux 2023年6月11日
    0118
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球