• spring boot 自定redis缓存注解


    功能要求

    自定义缓存注解,使用自定义缓存的注解时可以将方法的返回值缓存到redis中。
    类似与Spring cache的功能。

    pom

    项目的依赖

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-aopartifactId>
    dependency>
    
    
    <dependency>
       <groupId>org.redissongroupId>
        <artifactId>redisson-spring-boot-starterartifactId>
    dependency>
    <dependency>
         <groupId>com.alibaba.fastjson2groupId>
         <artifactId>fastjson2artifactId>
     dependency>
     <dependency>
         <groupId>org.projectlombokgroupId>
         <artifactId>lombokartifactId>
     dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    项目中redis配置

    # redis 配置
    redis:
      # 地址
      host: localhost
      # 端口,默认为6379
      port: 6379
      # 数据库索引
      database: 0
      # 密码
      password:
      # 连接超时时间
      timeout: 10s
      lettuce:
        pool:
          # 连接池中的最小空闲连接
          min-idle: 0
          # 连接池中的最大空闲连接
          max-idle: 8
          # 连接池的最大数据库连接数
          max-active: 8
          # #连接池最大阻塞等待时间(使用负值表示没有限制)
          max-wait: -1ms
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    redis配置

    在项目中创建config文件夹,在此文件夹中创建关于redis的相关配置。
    配置可以直接使用
    如下

    redis序列化

    /**
     * Redis使用FastJson序列化
     *
     * @author 张小三 
     */
    public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
        public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    
        private Class<T> clazz;
    
    
        public FastJson2JsonRedisSerializer(Class<T> clazz) {
            super();
            this.clazz = clazz;
        }
    
        @Override
        public byte[] serialize(T t) throws SerializationException {
            if (t == null) {
                return new byte[0];
            }
            return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
        }
    
        @Override
        public T deserialize(byte[] bytes) throws SerializationException {
            if (bytes == null || bytes.length <= 0) {
                return null;
            }
            String str = new String(bytes, DEFAULT_CHARSET);
    
            return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    redis配置

    
    /**
     * redis配置
     *
     * @author 张健泰
     */
    @Configuration
    @EnableCaching
    @AutoConfigureBefore(RedisAutoConfiguration.class)
    public class RedisConfig extends CachingConfigurerSupport
    {
        @Bean
        @SuppressWarnings(value = { "unchecked", "rawtypes" })
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
        {
            RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
            template.setConnectionFactory(connectionFactory);
    
            FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
    
            // 使用StringRedisSerializer来序列化和反序列化redis的key值
            template.setKeySerializer(new StringRedisSerializer());
            template.setValueSerializer(serializer);
    
            // Hash的key也采用StringRedisSerializer的序列化方式
            template.setHashKeySerializer(new StringRedisSerializer());
            template.setHashValueSerializer(serializer);
    
            template.afterPropertiesSet();
            return template;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    redis操作

    将redis的相关操作封装成类,以组件的形式注入到spring 容器中。

    @SuppressWarnings(value = {"unchecked", "rawtypes"})
    @Component(value = "RedisService")
    @Repository
    public class RedisService {
        @Autowired
        public RedisTemplate redisTemplate;
    
        /**
         * 缓存基本的对象,Integer、String、实体类等
         *
         * @param key   缓存的键值
         * @param value 缓存的值
         */
        public <T> void setCacheObject(final String key, final T value) {
            redisTemplate.opsForValue().set(key, value);
        }
    
        /**
         * 缓存基本的对象,Integer、String、实体类等
         *
         * @param key      缓存的键值
         * @param value    缓存的值
         * @param timeout  时间
         * @param timeUnit 时间颗粒度
         */
        public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) {
            redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
        }
    
        /**
         * 设置有效时间
         *
         * @param key     Redis键
         * @param timeout 超时时间
         * @return true=设置成功;false=设置失败
         */
        public boolean expire(final String key, final long timeout) {
            return expire(key, timeout, TimeUnit.SECONDS);
        }
    
        /**
         * 设置有效时间
         *
         * @param key     Redis键
         * @param timeout 超时时间
         * @param unit    时间单位
         * @return true=设置成功;false=设置失败
         */
        public boolean expire(final String key, final long timeout, final TimeUnit unit) {
            return redisTemplate.expire(key, timeout, unit);
        }
    
        /**
         * 获取有效时间
         *
         * @param key Redis键
         * @return 有效时间
         */
        public long getExpire(final String key) {
            return redisTemplate.getExpire(key);
        }
    
        /**
         * 判断 key是否存在
         *
         * @param key 键
         * @return true 存在 false不存在
         */
        public Boolean hasKey(String key) {
            return redisTemplate.hasKey(key);
        }
    
        /**
         * 获得缓存的基本对象。
         *
         * @param key 缓存键值
         * @return 缓存键值对应的数据
         */
        public <T> T getCacheObject(final String key) {
            ValueOperations<String, T> operation = redisTemplate.opsForValue();
            return operation.get(key);
        }
    
        /**
         * 删除单个对象
         *
         * @param key
         */
        public boolean deleteObject(final String key) {
            return redisTemplate.delete(key);
        }
    
        /**
         * 删除集合对象
         *
         * @param collection 多个对象
         * @return
         */
        public long deleteObject(final Collection collection) {
            return redisTemplate.delete(collection);
        }
    
        /**
         * 缓存List数据
         *
         * @param key      缓存的键值
         * @param dataList 待缓存的List数据
         * @return 缓存的对象
         */
        public <T> long setCacheList(final String key, final List<T> dataList) {
            Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
            return count == null ? 0 : count;
        }
    
        /**
         * 获得缓存的list对象
         *
         * @param key 缓存的键值
         * @return 缓存键值对应的数据
         */
        public <T> List<T> getCacheList(final String key) {
            return redisTemplate.opsForList().range(key, 0, -1);
        }
    
        /**
         * 缓存Set
         *
         * @param key     缓存键值
         * @param dataSet 缓存的数据
         * @return 缓存数据的对象
         */
        public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
            BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
            Iterator<T> it = dataSet.iterator();
            while (it.hasNext()) {
                setOperation.add(it.next());
            }
            return setOperation;
        }
    
        /**
         * 获得缓存的set
         *
         * @param key
         * @return
         */
        public <T> Set<T> getCacheSet(final String key) {
            return redisTemplate.opsForSet().members(key);
        }
    
        /**
         * 缓存Map
         *
         * @param key
         * @param dataMap
         */
        public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
            if (dataMap != null) {
                redisTemplate.opsForHash().putAll(key, dataMap);
            }
        }
    
        /**
         * 获得缓存的Map
         *
         * @param key
         * @return
         */
        public <T> Map<String, T> getCacheMap(final String key) {
            return redisTemplate.opsForHash().entries(key);
        }
    
        /**
         * 往Hash中存入数据
         *
         * @param key   Redis键
         * @param hKey  Hash键
         * @param value 值
         */
        public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
            redisTemplate.opsForHash().put(key, hKey, value);
        }
    
        /**
         * 获取Hash中的数据
         *
         * @param key  Redis键
         * @param hKey Hash键
         * @return Hash中的对象
         */
        public <T> T getCacheMapValue(final String key, final String hKey) {
            HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
            return opsForHash.get(key, hKey);
        }
    
        /**
         * 获取多个Hash中的数据
         *
         * @param key   Redis键
         * @param hKeys Hash键集合
         * @return Hash对象集合
         */
        public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
            return redisTemplate.opsForHash().multiGet(key, hKeys);
        }
    
        /**
         * 获得缓存的基本对象列表
         *
         * @param pattern 字符串前缀
         * @return 对象列表
         */
        public Collection<String> keys(final String pattern) {
            return redisTemplate.keys(pattern);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216

    创建自定义注解

    下项目的包下创建annimates文件夹,用于存放自定义注解以及注解的实现类。

    创建自定义缓存注解

    annimates文件夹下创建注解,如下:

    
    /**
     * redis 缓存注解
     */
    @Target({ ElementType.PARAMETER, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RedisCache {
    
        /**
         * 缓存的 key
         * @return
         */
        String key() default "";
    
        /**
         *  key名的后缀
         */
        String keySuffix() default "";
    
        /**
         *  key名的后缀使用指定第几个参数的值作为key后缀,参数从0开始。-1(默认)表示不用参数值作为key后缀值
         */
        int keySuffixIsParamIndex() default -1;
    
        /**
         * 缓存的key 前缀,
         */
        String keyPrefix() default "";
    
        /**
         * 未指定前缀时,是否在key之前插入包名+方法名作为前缀
         */
        boolean keyPrefixIsFunctionName() default true;
    
        /**
         * key的分隔符
         */
        String keySplitStr() default "::";
    
        /**
         * 缓存时长
         */
        long time() default 60L;
    
        /**
         * 缓存时长单位
         * @return
         */
        TimeUnit type() default TimeUnit.SECONDS;
    
        /**
         * 操作类型
         * select : 查询
         * update:更新
         * insert:插入
         * delete:删除
         * selectOrInsert: (默认)
         */
        String optionType() default "selectOrInsert";
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    自定义缓存注解的逻辑实现

    
    @Aspect
    @Slf4j
    @Component
    public class RedisCacheAopHandler {
        @Pointcut("@annotation(com.zhang.annimates.RedisCache)")
        public void annotationPoint() throws Throwable {
        }
    	/**
    	* 注入redis操作对象
    	*/
        @Autowired
        RedisService redisService;
    
        @SneakyThrows
        @Around(value = "annotationPoint() && @annotation(redisCache)",argNames = "proceedingJoinPoint,redisCache")
        public Object around(ProceedingJoinPoint proceedingJoinPoint,RedisCache redisCache){
            String redisKey = createKeyName(proceedingJoinPoint, redisCache);
            Object cacheObject = redisService.getCacheObject(redisKey);
    
            System.out.println("key=>"+redisKey);
            if (Objects.isNull(cacheObject)){
                // 不存在缓存中
                Object methodResult = proceedingJoinPoint.proceed(); //继续执行使用注解的方法内的代码逻辑, 得到方法的返回值
                //方法的返回值存入缓存中
                redisService.setCacheObject(redisKey,methodResult,redisCache.time(),redisCache.type());
                log.info("不存在缓存中,存入缓存:"+methodResult.toString());
                return methodResult; 
            }else{
                log.info("存在缓存中,缓存取出:"+cacheObject.toString());
                // 缓存命中后,直接返回缓存中的数据。并不执行方法内的逻辑。
                return cacheObject;
            }
        }
    
        /***
         * 生成缓存的 key 规则
         * @param redisCache
         * @return
         */
        private String createKeyName(ProceedingJoinPoint proceedingJoinPoint,RedisCache redisCache){
            String key = redisCache.key(); //key
            String keySuffix = redisCache.keySuffix(); //key名的后缀
            int keySuffixIsParamIndex = redisCache.keySuffixIsParamIndex(); //key名的后缀使用指定第几个参数的值作为key后缀,
            String keyPrefix = redisCache.keyPrefix();//缓存的key 前缀,
            boolean keyPrefixIsFunctionName = redisCache.keyPrefixIsFunctionName(); //未指定前缀时,是否在key之前插入包名+方法名作为前缀
            String keySplitStr = redisCache.keySplitStr(); //key的分隔符
    
            StringJoiner stringJoiner = new StringJoiner(keySplitStr);
            if (StringUtils.hasText(key) && keyPrefixIsFunctionName != true){
                // 前缀
                if (StringUtils.hasText(keyPrefix)){
                    stringJoiner.add(keyPrefix);
                }
    
                //后缀
                stringJoiner.add(key);
                if (StringUtils.hasText(keySuffix)){
                    stringJoiner.add(keySuffix);
                }else if (keySuffixIsParamIndex >= 0){
                    Object[] args = proceedingJoinPoint.getArgs();
                    if (Objects.isNull(args) == false && args.length > 0 && keySuffixIsParamIndex <= args.length-1){
                        stringJoiner.add(args[keySuffixIsParamIndex].toString());
                    }
                }
            }else if (keyPrefixIsFunctionName == true){
                //未指定前缀以及key,则使用当前注解所在的包名+方法名+[后缀|参数位置值]作为key
                String packageName = proceedingJoinPoint.getSignature().getDeclaringTypeName();//获取注解所在的包和类名
                String functionName = proceedingJoinPoint.getSignature().getName(); //获取注解作用的方法名称
                stringJoiner.add(packageName+"."+functionName);
    
                // 后缀
                if (StringUtils.hasText(keySuffix)){
                    stringJoiner.add(keySuffix);
                }else if (keySuffixIsParamIndex >= 0){
                    Object[] args = proceedingJoinPoint.getArgs();
                    if (Objects.isNull(args) == false && args.length > 0 && keySuffixIsParamIndex <= args.length-1){
                        stringJoiner.add(args[keySuffixIsParamIndex].toString());
                    }
                }
            }
            return stringJoiner.toString();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    在方法上使用自定义注解

    创建一个TestController控制器,并在此创建一个test方法,在此方法上使用注解

    @RestController
    @RequestMapping(value = "/testController")
    public class TestController {
    	
        @RequestMapping(value = "/test")
         @RedisCache(keySplitStr = "::",time = 1L,type = TimeUnit.DAYS)
        public String test(@RequestParam(value = "id")Integer id)throws Exception{
        	// todo 缓存未命中时的处理逻辑,用于数据查询,缓存命中后,不执行以下的代码。
        	
            System.out.println("======分隔线,缓存未命中则会运行到此=======");
            return "这是查询出存入缓存的数据";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    运行结果

    缓存中无数据

    控制台输出:

    key=>com.zhang.controller.TestController.test
    ======分隔线,缓存未命中则会运行到此=======
    不存在缓存中,存入缓存:这是查询出存入缓存的数据
    
    
    • 1
    • 2
    • 3
    • 4

    缓存中有数据

    控制台输出:

    key=>com.zhang.controller.TestController.test 
    存在缓存中,缓存取出:这是查询出存入缓存的数据
    
    
    • 1
    • 2
    • 3

    缓存命中后,并不会执行test方法中的 System.out.println(“分隔线,缓存未命中则会运行到此=”); 代码。

  • 相关阅读:
    flutter 合并数组及操作符
    同源策略,跨域,请求,网络安全详细知识
    基于ssm的宠物商城网站设计与实现
    数据结构与算法设计分析——动态规划
    二、进程管理(四)经典同步互斥问题
    3D孪生场景搭建:参数化模型
    SoftPlc on docker 测试
    分析并实现Android中的MVC、MVP架构模式
    记录--用 Vue 实现原神官网的角色切换效果
    RustDay05------Exercise[41-50]
  • 原文地址:https://blog.csdn.net/qq_38313548/article/details/126622399