zoukankan      html  css  js  c++  java
  • springboot2 整合 redis 并通过 aop 实现自定义注解

    1,相关依赖

    pom.xml 片段

    <!-- 面向切面 AOP -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
    <!-- Redis 访问依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- Redis 连接池依赖 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    

    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,简单的使用

    1,注入

    // 声明泛型的时候自动注入 @Autowired 会失效,这里使用 @Resource
    // 该类操作任意类型的数据,但是存入到 redis 的数据为二进制,难以查看
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    
    // 该类只能操作字符类型的数据,存入到 Redis 的为字符串,查看比较友好
    @Autowired
    private StringRedisTemplate redisTemplate;
    

    2,读取/写入,get/set

    redisTemplate.opsForValue().get(key);
    redisTemplate.opsForValue().set(key, value, cacheRedis.deadline(), TimeUnit.SECONDS);
    

    4,官方提供的注解

    1,原因

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

    2,Spring 提供的几个注解

    @Cacheable @CachePut @CacheEvict

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

    Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用

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

    5,自定义注解

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

    1,定义一个注解

    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 永不过期
    
    }
    

    2,定义一个 AOP 进行拦截

    主要思路就是,通过注解和挂有该注解方法的参数签名和值生成 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<String, Object> redisTemplate;
    
        /**
         * 环绕通知,拦截 redis 的写入和读取,如果方法执行结果为 null 不进行保存
         * @param point      切点对象
         * @param cacheRedis 注解内容
         */
        @Around(value = "@annotation(cacheRedis)", argNames = "point,cacheRedis")
        public Object aop(ProceedingJoinPoint point, CacheRedis cacheRedis) throws Throwable {
            Map<String, Object> 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<String, Object> mapParam(ProceedingJoinPoint point) {
            Map<String, Object> 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<String, Object> 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();
        }
    }
    

    3,简单的使用

    接下来我们就可以愉快的使用了,我们只需要在方法上声明注解,填入前缀,参与生成 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<UserMapper, User> {
    
        /**
         * 条件查询获取用户
         * @param name 用户昵称
         */
        @CacheRedis(prefix = "user", element = {"name"}, deadline = 120)
        public User getByName(String name) {
            LambdaQueryWrapper<User> wrapper = new QueryWrapper<User>().lambda();
            wrapper.eq(User::getName, name);
            return getOne(wrapper);
        }
    }
    
  • 相关阅读:
    软件工程第1次阅读作业
    软件工程第0次作业
    OO第四次博客
    OO第三次总结
    OO第二次总结
    OO第一次总结
    提问回顾与个人总结
    结对项目:最长单词链
    第一次阅读作业
    第四次博客
  • 原文地址:https://www.cnblogs.com/lovling/p/14543492.html
Copyright © 2011-2022 走看看