SpringBoot 整合Redisson重写cacheName支持多参数

依赖



    org.apache.commons
    commons-lang3



    cn.hutool
    hutool-extra




    org.redisson
    redisson-spring-boot-starter
    ${redisson.version}

配置文件

spring:
  data:
    redis:
      # 地址
      host: localhost
      # 端口,默认为6379
      port: 6379
      # 数据库索引
      database: 0
      # 密码(如没有密码请注释掉)
#      password: ******
      # 连接超时时间
      timeout: 10s
      # 是否开启ssl
      ssl:
        enabled: false

redisson:
  # redis key前缀
  keyPrefix:
  # 线程池数量
  threads: 4
  # Netty线程池数量
  nettyThreads: 8
  # 单节点配置
  singleServerConfig:
    # 客户端名称
    clientName: ${ruoyi.name}
    # 最小空闲连接数
    connectionMinimumIdleSize: 8
    # 连接池大小
    connectionPoolSize: 32
    # 连接空闲超时,单位:毫秒
    idleConnectionTimeout: 10000
    # 命令等待超时,单位:毫秒
    timeout: 3000
    # 发布和订阅连接池大小
    subscriptionConnectionPoolSize: 50

Properties

package com.example.redisson.config.properties;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.redisson.config.ReadMode;
import org.redisson.config.SubscriptionMode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * Redisson 配置属性
 *
 * @author Lion Li
 */
@Data
@Component
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {

    /**
     * redis缓存key前缀
     */
    private String keyPrefix;

    /**
     * 线程池数量,默认值 = 当前处理核数量 * 2
     */
    private int threads;

    /**
     * Netty线程池数量,默认值 = 当前处理核数量 * 2
     */
    private int nettyThreads;

    /**
     * 单机服务配置
     */
    private SingleServerConfig singleServerConfig;

    /**
     * 集群服务配置
     */
    private ClusterServersConfig clusterServersConfig;

    @Data
    @NoArgsConstructor
    public static class SingleServerConfig {

        /**
         * 客户端名称
         */
        private String clientName;

        /**
         * 最小空闲连接数
         */
        private int connectionMinimumIdleSize;

        /**
         * 连接池大小
         */
        private int connectionPoolSize;

        /**
         * 连接空闲超时,单位:毫秒
         */
        private int idleConnectionTimeout;

        /**
         * 命令等待超时,单位:毫秒
         */
        private int timeout;

        /**
         * 发布和订阅连接池大小
         */
        private int subscriptionConnectionPoolSize;

    }

    @Data
    @NoArgsConstructor
    public static class ClusterServersConfig {

        /**
         * 客户端名称
         */
        private String clientName;

        /**
         * master最小空闲连接数
         */
        private int masterConnectionMinimumIdleSize;

        /**
         * master连接池大小
         */
        private int masterConnectionPoolSize;

        /**
         * slave最小空闲连接数
         */
        private int slaveConnectionMinimumIdleSize;

        /**
         * slave连接池大小
         */
        private int slaveConnectionPoolSize;

        /**
         * 连接空闲超时,单位:毫秒
         */
        private int idleConnectionTimeout;

        /**
         * 命令等待超时,单位:毫秒
         */
        private int timeout;

        /**
         * 发布和订阅连接池大小
         */
        private int subscriptionConnectionPoolSize;

        /**
         * 读取模式
         */
        private ReadMode readMode;

        /**
         * 订阅模式
         */
        private SubscriptionMode subscriptionMode;

    }

}

Config

注入自定义PlusSpringCacheManager支持多参数

package org.dromara.common.redis.config;

import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.redis.config.properties.RedissonProperties;
import org.dromara.common.redis.handler.KeyPrefixHandler;
import org.dromara.common.redis.manager.PlusSpringCacheManager;
import org.redisson.client.codec.StringCodec;
import org.redisson.codec.CompositeCodec;
import org.redisson.codec.TypedJsonJacksonCodec;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;

/**
 * redis配置
 *
 * @author Lion Li
 */
@Slf4j
@AutoConfiguration
@EnableCaching
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig {

    @Autowired
    private RedissonProperties redissonProperties;

    @Autowired
    private ObjectMapper objectMapper;

    @Bean
    public RedissonAutoConfigurationCustomizer redissonCustomizer() {
        return config -> {
            ObjectMapper om = objectMapper.copy();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            // 指定序列化输入的类型,类必须是非final修饰的。序列化时将对象全类名一起保存下来
            om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
            TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om);
            // 组合序列化 key 使用 String 内容使用通用 json 格式
            CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);
            config.setThreads(redissonProperties.getThreads())
                .setNettyThreads(redissonProperties.getNettyThreads())
                // 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
                .setUseScriptCache(true)
                .setCodec(codec);
            RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
            if (ObjectUtil.isNotNull(singleServerConfig)) {
                // 使用单机模式
                config.useSingleServer()
                    //设置redis key前缀
                    .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
                    .setTimeout(singleServerConfig.getTimeout())
                    .setClientName(singleServerConfig.getClientName())
                    .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
                    .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
                    .setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
                    .setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
            }
            // 集群配置方式 参考下方注释
            RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
            if (ObjectUtil.isNotNull(clusterServersConfig)) {
                config.useClusterServers()
                    //设置redis key前缀
                    .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
                    .setTimeout(clusterServersConfig.getTimeout())
                    .setClientName(clusterServersConfig.getClientName())
                    .setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
                    .setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
                    .setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
                    .setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
                    .setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
                    .setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
                    .setReadMode(clusterServersConfig.getReadMode())
                    .setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
            }
            log.info("初始化 redis 配置");
        };
    }

    /**
     * 自定义缓存管理器 整合spring-cache
     */
    @Bean
    public CacheManager cacheManager() {
        return new PlusSpringCacheManager();
    }

    /**
     * redis集群配置 yml
     *
     * --- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉)
     * spring.data:
     *   redis:
     *     cluster:
     *       nodes:
     *         - 192.168.0.100:6379
     *         - 192.168.0.101:6379
     *         - 192.168.0.102:6379
     *     # 密码
     *     password:
     *     # 连接超时时间
     *     timeout: 10s
     *     # 是否开启ssl
     *     ssl.enabled: false
     *
     * redisson:
     *   # 线程池数量
     *   threads: 16
     *   # Netty线程池数量
     *   nettyThreads: 32
     *   # 集群配置
     *   clusterServersConfig:
     *     # 客户端名称
     *     clientName: ${ruoyi.name}
     *     # master最小空闲连接数
     *     masterConnectionMinimumIdleSize: 32
     *     # master连接池大小
     *     masterConnectionPoolSize: 64
     *     # slave最小空闲连接数
     *     slaveConnectionMinimumIdleSize: 32
     *     # slave连接池大小
     *     slaveConnectionPoolSize: 64
     *     # 连接空闲超时,单位:毫秒
     *     idleConnectionTimeout: 10000
     *     # 命令等待超时,单位:毫秒
     *     timeout: 3000
     *     # 发布和订阅连接池大小
     *     subscriptionConnectionPoolSize: 50
     *     # 读取模式
     *     readMode: "SLAVE"
     *     # 订阅模式
     *     subscriptionMode: "MASTER"
     */

}

重写cacheName方法

/**
 * Copyright (c) 2013-2021 Nikita Koksharov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.dromara.common.redis.manager;

import org.dromara.common.redis.utils.RedisUtils;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
import org.redisson.spring.cache.CacheConfig;
import org.redisson.spring.cache.RedissonCache;
import org.springframework.boot.convert.DurationStyle;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * A {@link org.springframework.cache.CacheManager} implementation
 * backed by Redisson instance.
 * 

* 修改 RedissonSpringCacheManager 源码 * 重写 cacheName 处理方法 支持多参数 * * @author Nikita Koksharov * */ @SuppressWarnings("unchecked") public class PlusSpringCacheManager implements CacheManager { private boolean dynamic = true; private boolean allowNullValues = true; private boolean transactionAware = true; Map<String, CacheConfig> configMap = new ConcurrentHashMap<>(); ConcurrentMap<String, Cache> instanceMap = new ConcurrentHashMap<>(); /** * Creates CacheManager supplied by Redisson instance */ public PlusSpringCacheManager() { } /** * Defines possibility of storing {@code null} values. *

* Default is true * * @param allowNullValues stores if true */ public void setAllowNullValues(boolean allowNullValues) { this.allowNullValues = allowNullValues; } /** * Defines if cache aware of Spring-managed transactions. * If {@code true} put/evict operations are executed only for successful transaction in after-commit phase. *

* Default is false * * @param transactionAware cache is transaction aware if true */ public void setTransactionAware(boolean transactionAware) { this.transactionAware = transactionAware; } /** * Defines 'fixed' cache names. * A new cache instance will not be created in dynamic for non-defined names. *

* `null` parameter setups dynamic mode * * @param names of caches */ public void setCacheNames(Collection<String> names) { if (names != null) { for (String name : names) { getCache(name); } dynamic = false; } else { dynamic = true; } } /** * Set cache config mapped by cache name * * @param config object */ public void setConfig(Map<String, ? extends CacheConfig> config) { this.configMap = (Map<String, CacheConfig>) config; } protected CacheConfig createDefaultConfig() { return new CacheConfig(); } @Override public Cache getCache(String name) { // 重写 cacheName 支持多参数 String[] array = StringUtils.delimitedListToStringArray(name, "#"); name = array[0]; Cache cache = instanceMap.get(name); if (cache != null) { return cache; } if (!dynamic) { return cache; } CacheConfig config = configMap.get(name); if (config == null) { config = createDefaultConfig(); configMap.put(name, config); } if (array.length > 1) { config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis()); } if (array.length > 2) { config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis()); } if (array.length > 3) { config.setMaxSize(Integer.parseInt(array[3])); } if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) { return createMap(name, config); } return createMapCache(name, config); } private Cache createMap(String name, CacheConfig config) { RMap<Object, Object> map = RedisUtils.getClient().getMap(name); Cache cache = new RedissonCache(map, allowNullValues); if (transactionAware) { cache = new TransactionAwareCacheDecorator(cache); } Cache oldCache = instanceMap.putIfAbsent(name, cache); if (oldCache != null) { cache = oldCache; } return cache; } private Cache createMapCache(String name, CacheConfig config) { RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name); Cache cache = new RedissonCache(map, config, allowNullValues); if (transactionAware) { cache = new TransactionAwareCacheDecorator(cache); } Cache oldCache = instanceMap.putIfAbsent(name, cache); if (oldCache != null) { cache = oldCache; } else { map.setMaxSize(config.getMaxSize()); } return cache; } @Override public Collection<String> getCacheNames() { return Collections.unmodifiableSet(configMap.keySet()); } }

支持多参数

key 格式为 cacheNames#ttl#maxIdleTime#maxSize
ttl 过期时间 如果设置为0则不过期 默认为0
maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500

package com.example.redisson.core;


public interface CacheNames {

    /**
     * 演示案例
     */
    String DEMO_CACHE = "demo:cache#60s#10m#20";

    /**
     * 系统配置
     */
    String SYS_CONFIG = "sys_config";

    /**
     * 数据字典
     */
    String SYS_DICT = "sys_dict";

    /**
     * 租户
     */
    String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";

    /**
     * 用户账户
     */
    String SYS_USER_NAME = "sys_user_name#30d";

    /**
     * 用户名称
     */
    String SYS_NICKNAME = "sys_nickname#30d";

    /**
     * 部门
     */
    String SYS_DEPT = "sys_dept#30d";

    /**
     * OSS内容
     */
    String SYS_OSS = "sys_oss#30d";

    /**
     * OSS配置
     */
    String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";

    /**
     * 在线用户
     */
    String ONLINE_TOKEN = "online_tokens";

}

使用

@Cacheable会先判断有没有缓存,如果有则不执行方法体
@CachePut无论如何都会先执行方法体然后将缓存保存供其他地方使用

package com.example.redisson.controller;

import com.example.redisson.core.CacheNames;
import com.example.redisson.core.R;
import com.example.redisson.utils.RedisUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;

/**
 * spring-cache 演示案例
 *
 * @author Lion Li
 */
// 类级别 缓存统一配置
//@CacheConfig(cacheNames = CacheNames.DEMO_CACHE)
@RequiredArgsConstructor
@RestController
@RequestMapping("/demo/cache")
public class RedisCacheController {

    /**
     * 测试 @Cacheable
     * 

* 表示这个方法有了缓存的功能,方法的返回值会被缓存下来 * 下一次调用该方法前,会去检查是否缓存中已经有值 * 如果有就直接返回,不调用方法 * 如果没有,就调用方法,然后把结果缓存起来 * 这个注解「一般用在查询方法上」 *

* 重点说明: 缓存注解严谨与其他筛选数据功能一起使用 * 例如: 数据权限注解 会造成 缓存击穿 与 数据不一致问题 *

* cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数 */ @Cacheable(cacheNames = "demo:cache#60s#10m#20", key = "#key", condition = "#key != null") @GetMapping("/test1") public R<String> test1(String key, String value) { System.out.println("test1-->调用方法体"); return R.ok("操作成功", value); } /** * 测试 @CachePut *

* 加了@CachePut注解的方法,会把方法的返回值put到缓存里面缓存起来,供其它地方使用 * 它「通常用在新增或者实时更新方法上」 *

* cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数 */ @CachePut(cacheNames = CacheNames.DEMO_CACHE, key = "#key", condition = "#key != null") @GetMapping("/test2") public R<String> test2(String key, String value) { System.out.println("test2-->调用方法体"); return R.ok("操作成功", value); } /** * 测试 @CacheEvict *

* 使用了CacheEvict注解的方法,会清空指定缓存 * 「一般用在删除的方法上」 *

* cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数 */ @CacheEvict(cacheNames = CacheNames.DEMO_CACHE, key = "#key", condition = "#key != null") @GetMapping("/test3") public R<String> test3(String key, String value) { return R.ok("操作成功", value); } /** * 测试设置过期时间 * 手动设置过期时间10秒 * 11秒后获取 判断是否相等 */ @GetMapping("/test6") public R<Boolean> test6(String key, String value) { RedisUtils.setCacheObject(key, value); boolean flag = RedisUtils.expire(key, Duration.ofSeconds(10)); System.out.println("***********" + flag); try { Thread.sleep(11 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } Object obj = RedisUtils.getCacheObject(key); return R.ok(value.equals(obj)); } }

你可能感兴趣的:(SpringBoot,spring,boot,spring,java)