基于spring-boot-starter-data-redis 和 redisson 的多数据源改造

背景

由于业务场景需要,在一个项目中可能存在多个 redis 实例,官方提供的 starter 只能支持单数据源,遂根据官方提供的 API 以及 Mybatis-plus 动态数据源的代码思路自己整合了一套 starter。

实现的效果演示

配置信息 多数据源

yaml 复制代码
# 动态数据源
spring:
  dynamic:
    redis:
      primary: master
      datasource:
        master:
          database: 1
          url: "redis://password@redis_host1:6379"
          key-prefix: "app:user:"
          enable-key-prefix: true
        master2:
          database: 2
          host: "redis_host2"
          password: password
          port: 6379
          key-prefix: "app:order:"
          enable-key-prefix: true
        clusterMaster:
          cluster:
            nodes:
             - redis_cluster_host:6379
             - redis_cluster_host:6380
             - redis_cluster_host:6381
          password: password
          key-prefix: "app:product:"
          enable-key-prefix: true
app:
  redis:
    enableLog: true

兼容原有单数据源

yaml 复制代码
# 单机
spring:
  redis:
    password: password
    port: 6379
    database: 0
    url: "redis://redis_host:6379"


# 集群
spring:
  redis:
    cluster:
      nodes:
        - redis_cluster_host:6379
        - redis_cluster_host:6380
        - redis_cluster_host:6381
    password: password

完整配置文件属性

java 复制代码
@Getter
@Setter
public class RedisRegisterProperties {

    /**
     * 数据库下标
     */
    private int database = 0;

    /**
     * 连接 URL,可覆盖host、port和password的配置。用户如果没有可以忽略。 示例:redis://user:password@example.com:6379
     */
    private String url;

    /**
     * 连接的host地址,url配置了,此处则会失效
     */
    private String host = "localhost";

    /**
     * 鉴权的用户名,url配置了,此处则会失效
     */
    private String username;

    /**
     * 鉴权的密码,url配置了,此处则会失效
     */
    private String password;

    /**
     * 是否使用SSL
     */
    private Boolean useSsl = false;

    /**
     * 连接的端口地址,url配置了,此处则会失效
     */
    private int port;

    /**
     * Key前缀
     */
    private String keyPrefix;

    /**
     * 是否启用Key前缀功能
     */
    private Boolean enableKeyPrefix = false;

    /**
     * 默认缓存管理器缓存过期时间,默认300,单位秒
     */
    private Integer cacheManagerDefaultTtl = 300;

    // *********************连接池 配置**************************************

    /**
     * 读取超时时间
     */
    private Duration timeout = Duration.ofSeconds(10);

    /**
     * 连接超时时间
     */
    private Duration connectTimeout = Duration.ofSeconds(30);

    /**
     * 最大等待时间
     */
    private Duration maxWait = Duration.ofSeconds(30);

    /**
     * 最大连接数
     */
    private int maxActive = 32;

    /**
     * 最大空闲连接数
     */
    private int maxIdle = 32;

    /**
     * 最小空闲连接数
     */
    private int minIdle = 16;


    // *********************哨兵模式、集群模式 配置**************************************

    /**
     * 哨兵模式配置
     */
    private Sentinel sentinel;

    /**
     * 集群模式配置
     */
    private Cluster cluster;

    // *********************redisson 配置**************************************

    /**
     * redisson transportMode:1.NIO、2.EPOLL、3.KQUEUE
     */
    private String transportMode = "NIO";

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

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

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

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

}

启动时自动注入 Bean

1. spring-boot-starter-data-redis 多数据源

定义配置

properties 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\  
com.base.redis.DynamicRedisConfig
java 复制代码
@Slf4j
@EnableCaching
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@AutoConfigureBefore(RedisAutoConfiguration.class)
@ConfigurationPropertiesScan(basePackageClasses = DynamicRedisRegisterProperties.class)
@Import(RedisHealthIndicatorConfiguration.class)
public class DynamicRedisConfig {


    @Bean
    public BeanDefinitionRegistryPostProcessor dynamicRedisRegistryPostProcessor(Environment environment) {
        return new DynamicRedisRegistryPostProcessor(environment);
    }

    /**
     * 注入默认序列化类,防止其他地方有使用到
     * @return
     */
    @Primary
    @Bean
    public Jackson2JsonRedisSerializer jackson2JsonRedisSerializer() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(),
            ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        final SimpleModule module = new SimpleModule();
        module.addDeserializer(String.class, new CustomStringDeserializer());
        objectMapper.registerModules(new JavaTimeModule(), module);

        Jackson2JsonRedisSerializer jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        jsonRedisSerializer.setObjectMapper(objectMapper);
        return jsonRedisSerializer;
    }


}

注入RedisPostProcessor

java 复制代码
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Cluster;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.env.Environment;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConfiguration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.util.ClassUtils;

/**
 * <p>@author chenmingjun </p>
 * <p>@date 2024-03-13 11:59</p>
 * <p>@since 1.0.0</p>
 */
@Slf4j
public class DynamicRedisRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    private static final String PRIMARY_DEFAULT_KEY = "primary";

    private final Environment environment;

    public DynamicRedisRegistryPostProcessor(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 默认的 redis 配置
        BindResult<RedisProperties> defaultResult = Binder.get(environment).bind("spring.redis", RedisProperties.class);
        // 动态redis配置
        BindResult<DynamicRedisRegisterProperties> dynamicResult = Binder.get(environment)
                .bind(DynamicRedisRegisterProperties.PREFIX, DynamicRedisRegisterProperties.class);
        DynamicRedisRegisterProperties properties = null;
        if (!dynamicResult.isBound()) {
            // 动态redis配置不存在,默认配置存在则传递默认配置的数据到 动态redis配置中
            if (defaultResult.isBound()) {
                properties = copyProperties(defaultResult.get(), new DynamicRedisRegisterProperties());
            }
        } else {
            properties = dynamicResult.get();
        }
        if (properties == null) {
            return;
        }
        log.info("==============register redisTemplate=======================");
        String primary = properties.getPrimary();
        Map<String, RedisRegisterProperties> datasource = properties.getDatasource();
        if (datasource == null) {
            log.info("RedisRegisterProperties.datasource is null! ");
            return;
        }
        boolean multipleDataSource;
        if (datasource.size() > 1) {
            // 存在多个数据源的情况注册方式有所变化
            multipleDataSource = true;
        } else {
            multipleDataSource = false;
        }
        datasource.forEach((key, redisRegisterProperties) -> realRegister(registry, key, redisRegisterProperties, primary, multipleDataSource));
        log.info("==============register redisTemplate=======================end");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    /**
     * 拷贝基本数据源的配置到动态数据源配置中(这里是兼容的是原有的单数据源配置)
     *
     * @param redisProperties
     * @param properties
     * @return
     */
    private DynamicRedisRegisterProperties copyProperties(RedisProperties redisProperties, DynamicRedisRegisterProperties properties) {
        Map<String, RedisRegisterProperties> datasource = properties.getDatasource() != null ? properties.getDatasource() : new ConcurrentHashMap<>();
        datasource.compute(PRIMARY_DEFAULT_KEY, (k, primary) -> {
            if (primary == null) {
                primary = new RedisRegisterProperties();
            }
            // 单机配置信息
            primary.setDatabase(redisProperties.getDatabase());
            primary.setUrl(redisProperties.getUrl());
            primary.setHost(redisProperties.getHost());
            primary.setPort(redisProperties.getPort());
            primary.setKeyPrefix(null);
            primary.setEnableKeyPrefix(false);
            if (redisProperties.getTimeout() != null) {
                primary.setTimeout(redisProperties.getTimeout());
            }
            if (redisProperties.getConnectTimeout() != null) {
                primary.setConnectTimeout(redisProperties.getConnectTimeout());
            }
            // 集群配置信息
            primary.setCluster(redisProperties.getCluster());
            // 基础账密信息
            primary.setUsername(redisProperties.getUsername());
            primary.setPassword(redisProperties.getPassword());
            return primary;
        });
        properties.setDatasource(datasource);
        properties.setPrimary(PRIMARY_DEFAULT_KEY);
        return properties;
    }

    /**
     * 实际注册逻辑,目前只实现了单机和集群的两种模式
     *
     * @param key
     * @param redisRegisterProperties
     * @param primary
     */
    private void realRegister(BeanDefinitionRegistry registry, String key, RedisRegisterProperties redisRegisterProperties, String primary,
                              boolean multipleDataSource) {
        // 单机配置
        String host = redisRegisterProperties.getHost();
        int port = redisRegisterProperties.getPort();
        // url配置
        String url = redisRegisterProperties.getUrl();
        if ((!StringUtils.equals("localhost", host) && port != 0) || StringUtils.isNotBlank(url)) {
            // 单机逻辑
            registerSingleMode(registry, key, redisRegisterProperties, primary, multipleDataSource);
        } else if (redisRegisterProperties.getCluster() != null) {
            // 集群逻辑
            registerClusterMode(registry, key, redisRegisterProperties, primary, multipleDataSource);
        }
    }

    /**
     * 单机模式注册
     *
     * @param registry
     * @param key
     * @param redisRegisterProperties
     * @param primary
     */
    private void registerSingleMode(BeanDefinitionRegistry registry, String key, RedisRegisterProperties redisRegisterProperties, String primary,
                                    boolean multipleDataSource) {
        String url = redisRegisterProperties.getUrl();
        boolean isPrimary = StringUtils.equals(primary, key);
        int database = redisRegisterProperties.getDatabase();

        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        if (StringUtils.isNotBlank(url)) {
            ConnectionInfoHelper connectionInfo = ConnectionInfoHelper.parseUrl(url);
            config.setHostName(connectionInfo.getHostName());
            config.setPort(connectionInfo.getPort());
            config.setUsername(redisRegisterProperties.getUsername() != null ?
                    redisRegisterProperties.getUsername() : connectionInfo.getUsername());
            config.setPassword(redisRegisterProperties.getPassword() != null ?
                    RedisPassword.of(redisRegisterProperties.getPassword()) : RedisPassword.of(connectionInfo.getPassword()));
        } else {
            config.setHostName(redisRegisterProperties.getHost());
            config.setPort(redisRegisterProperties.getPort());
            config.setUsername(redisRegisterProperties.getUsername());
            config.setPassword(RedisPassword.of(redisRegisterProperties.getPassword()));
        }
        config.setDatabase(database);

        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxTotal(redisRegisterProperties.getMaxActive());
        genericObjectPoolConfig.setMaxWait(redisRegisterProperties.getMaxWait());
        genericObjectPoolConfig.setMaxIdle(redisRegisterProperties.getMaxIdle());
        genericObjectPoolConfig.setMinIdle(redisRegisterProperties.getMinIdle());

        // 注册连接信息
        registerConnection(registry, key, genericObjectPoolConfig, redisRegisterProperties, isPrimary, config, multipleDataSource);
    }

    /**
     * 集群模式注册
     *
     * @param registry
     * @param key
     * @param redisRegisterProperties
     * @param primary
     */
    private void registerClusterMode(BeanDefinitionRegistry registry, String key, RedisRegisterProperties redisRegisterProperties, String primary,
                                     boolean multipleDataSource) {
        boolean isPrimary = StringUtils.equals(primary, key);
        Cluster cluster = redisRegisterProperties.getCluster();
        List<String> nodes = cluster.getNodes();
        Integer maxRedirects = cluster.getMaxRedirects();
        if (CollectionUtils.isEmpty(nodes)) {
            throw new IllegalStateException("cluster nodes is empty");
        }
        RedisClusterConfiguration config = new RedisClusterConfiguration(nodes);
        if (maxRedirects != null) {
            config.setMaxRedirects(maxRedirects);
        }
        config.setUsername(redisRegisterProperties.getUsername());
        if (redisRegisterProperties.getPassword() != null) {
            config.setPassword(RedisPassword.of(redisRegisterProperties.getPassword()));
        }

        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxTotal(redisRegisterProperties.getMaxActive());
        genericObjectPoolConfig.setMaxWait(redisRegisterProperties.getMaxWait());
        genericObjectPoolConfig.setMaxIdle(redisRegisterProperties.getMaxIdle());
        genericObjectPoolConfig.setMinIdle(redisRegisterProperties.getMinIdle());

        // 注册连接信息
        registerConnection(registry, key, genericObjectPoolConfig, redisRegisterProperties, isPrimary, config, multipleDataSource);
    }

    /**
     * 注册连接信息
     *
     * @param registry
     * @param key
     * @param genericObjectPoolConfig
     * @param redisRegisterProperties
     * @param isPrimary
     * @param config
     */
    private void registerConnection(BeanDefinitionRegistry registry, String key, GenericObjectPoolConfig genericObjectPoolConfig,
                                    RedisRegisterProperties redisRegisterProperties, boolean isPrimary, RedisConfiguration config, boolean multipleDataSource) {
        // 注册connectFactory
        String redisFactoryBeanName = key.concat(RedisComponentEnum.CONNECT_FACTORY.getBeanNameSuffix());
        AbstractBeanDefinition rawBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(LettuceConnectionFactory.class,
                () -> {
                    LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
                    builder.poolConfig(genericObjectPoolConfig);
                    builder.commandTimeout(redisRegisterProperties.getTimeout());
                    return new LettuceConnectionFactory(config, builder.build());
                }).getRawBeanDefinition();
        rawBeanDefinition.setPrimary(isPrimary);

        RedisComponentHelper.registerBean(registry, redisFactoryBeanName, PersistenceExceptionTranslator.class,
                rawBeanDefinition);
        RedisComponentHelper.addComponent(RedisComponentEnum.CONNECT_FACTORY, key, redisFactoryBeanName, isPrimary);

        // 注册基础组件
        registerNormalComponent(registry, key, isPrimary, redisFactoryBeanName, redisRegisterProperties, multipleDataSource);

        // 注册响应式组件
        registerReactiveComponent(registry, key, isPrimary, redisFactoryBeanName, redisRegisterProperties);

        // 注册缓存管理器
        registerCacheManager(registry, key, isPrimary, redisFactoryBeanName, redisRegisterProperties);
    }

    /**  
    * 注册缓存管理器  
    * @param registry  
    * @param key  
    * @param isPrimary  
    * @param factoryBeanDefinitionName  
    * @param properties  
    */
    private void registerCacheManager(BeanDefinitionRegistry registry, String key,
                                      boolean isPrimary, String factoryBeanDefinitionName, RedisRegisterProperties properties) {

        // 注册CacheManager
        String redisCacheManagerBeanName = key.concat(RedisComponentEnum.CACHE_MANAGER.getBeanNameSuffix());
        AbstractBeanDefinition rawBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AdvanceCacheManager.class)
                .setFactoryMethod("createInstance")
                .addConstructorArgReference(factoryBeanDefinitionName)
                .addConstructorArgValue(properties)
                .getRawBeanDefinition();
        rawBeanDefinition.setPrimary(isPrimary);
        RedisComponentHelper.registerBean(registry, redisCacheManagerBeanName, AdvanceCacheManager.class,
                rawBeanDefinition);
        RedisComponentHelper.addComponent(RedisComponentEnum.CACHE_MANAGER, key, redisCacheManagerBeanName, isPrimary);
    }


    /**
     * 注册基础组件
     *
     * @param key
     * @param isPrimary
     */
    private void registerNormalComponent(BeanDefinitionRegistry registry, String key,
                                         boolean isPrimary, String factoryBeanDefinitionName, RedisRegisterProperties properties, boolean multipleDataSource) {
        if (multipleDataSource) {
            // 多数据源
            // 注册redisTemplate
            AbstractBeanDefinition redisTemplateBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AdvanceRedisTemplate.class)
                    .addConstructorArgReference(factoryBeanDefinitionName)
                    .addConstructorArgValue(properties)
                    .addConstructorArgValue(key.concat(RedisComponentEnum.REDIS_TEMPLATE.getBeanNameSuffix()))
                    .getRawBeanDefinition();
            redisTemplateBeanDefinition.setPrimary(isPrimary);
            RedisComponentHelper.registerBean(registry, key.concat(RedisComponentEnum.REDIS_TEMPLATE.getBeanNameSuffix()), AdvanceRedisTemplate.class, redisTemplateBeanDefinition);
            RedisComponentHelper.addComponent(RedisComponentEnum.REDIS_TEMPLATE, key, key.concat(RedisComponentEnum.REDIS_TEMPLATE.getBeanNameSuffix()),
                    isPrimary);

            // 注册stringRedisTemplate
            AbstractBeanDefinition stringRedisTemplateBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AdvanceStringRedisTemplate.class)
                    .addConstructorArgReference(factoryBeanDefinitionName)
                    .addConstructorArgValue(properties)
                    .addConstructorArgValue(key.concat(RedisComponentEnum.STRING_REDIS_TEMPLATE.getBeanNameSuffix()))
                    .getRawBeanDefinition();
            stringRedisTemplateBeanDefinition.setPrimary(isPrimary);
            RedisComponentHelper.registerBean(registry, key.concat(RedisComponentEnum.STRING_REDIS_TEMPLATE.getBeanNameSuffix()), AdvanceStringRedisTemplate.class,
                    stringRedisTemplateBeanDefinition);
            RedisComponentHelper.addComponent(RedisComponentEnum.STRING_REDIS_TEMPLATE, key,
                    key.concat(RedisComponentEnum.STRING_REDIS_TEMPLATE.getBeanNameSuffix()), isPrimary);
            if (isPrimary) {
                // 兼容 org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.redisTemplate 注入流程的启动问题
                AbstractBeanDefinition primaryRedisTemplateBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AdvanceRedisTemplate.class)
                        .addConstructorArgReference(factoryBeanDefinitionName)
                        .addConstructorArgValue(properties)
                        .addConstructorArgValue(RedisComponentEnum.REDIS_TEMPLATE.getPrimaryBeanName())
                        .getRawBeanDefinition();
                RedisComponentHelper.registerBean(registry, RedisComponentEnum.REDIS_TEMPLATE.getPrimaryBeanName(), AdvanceRedisTemplate.class, primaryRedisTemplateBeanDefinition);
                RedisComponentHelper.addComponent(RedisComponentEnum.REDIS_TEMPLATE, key, RedisComponentEnum.REDIS_TEMPLATE.getPrimaryBeanName(), false);

                // 兼容 org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.stringRedisTemplate 注入流程的启动问题
                AbstractBeanDefinition primaryStringRedisTemplateBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AdvanceStringRedisTemplate.class)
                        .addConstructorArgReference(factoryBeanDefinitionName)
                        .addConstructorArgValue(properties)
                        .addConstructorArgValue(RedisComponentEnum.STRING_REDIS_TEMPLATE.getPrimaryBeanName())
                        .getRawBeanDefinition();
                RedisComponentHelper.registerBean(registry, RedisComponentEnum.STRING_REDIS_TEMPLATE.getPrimaryBeanName(), AdvanceStringRedisTemplate.class,
                        primaryStringRedisTemplateBeanDefinition);
                RedisComponentHelper.addComponent(RedisComponentEnum.STRING_REDIS_TEMPLATE, key, RedisComponentEnum.STRING_REDIS_TEMPLATE.getPrimaryBeanName(),
                        false);
            }
        } else {
            // 单数据源
            if (isPrimary) {
                // 兼容 org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.redisTemplate 注入流程的启动问题
                AbstractBeanDefinition primaryRedisTemplateBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AdvanceRedisTemplate.class)
                        .addConstructorArgReference(factoryBeanDefinitionName)
                        .addConstructorArgValue(properties)
                        .addConstructorArgValue(RedisComponentEnum.REDIS_TEMPLATE.getPrimaryBeanName())
                        .getRawBeanDefinition();
                RedisComponentHelper.registerBean(registry, RedisComponentEnum.REDIS_TEMPLATE.getPrimaryBeanName(), AdvanceRedisTemplate.class, primaryRedisTemplateBeanDefinition);
                RedisComponentHelper.addComponent(RedisComponentEnum.REDIS_TEMPLATE, key, RedisComponentEnum.REDIS_TEMPLATE.getPrimaryBeanName(), false);

                // 兼容 org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.stringRedisTemplate 注入流程的启动问题
                AbstractBeanDefinition primaryStringRedisTemplateBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AdvanceStringRedisTemplate.class)
                        .addConstructorArgReference(factoryBeanDefinitionName)
                        .addConstructorArgValue(properties)
                        .addConstructorArgValue(RedisComponentEnum.STRING_REDIS_TEMPLATE.getPrimaryBeanName())
                        .getRawBeanDefinition();
                RedisComponentHelper.registerBean(registry, RedisComponentEnum.STRING_REDIS_TEMPLATE.getPrimaryBeanName(), AdvanceStringRedisTemplate.class,
                        primaryStringRedisTemplateBeanDefinition);
                RedisComponentHelper.addComponent(RedisComponentEnum.STRING_REDIS_TEMPLATE, key, RedisComponentEnum.STRING_REDIS_TEMPLATE.getPrimaryBeanName(),
                        false);
            } else {
                // 注册redisTemplate
                AbstractBeanDefinition redisTemplateBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AdvanceRedisTemplate.class)
                        .addConstructorArgReference(factoryBeanDefinitionName)
                        .addConstructorArgValue(properties)
                        .addConstructorArgValue(key.concat(RedisComponentEnum.REDIS_TEMPLATE.getBeanNameSuffix()))
                        .getRawBeanDefinition();
//            redisTemplateBeanDefinition.setPrimary(isPrimary);
                RedisComponentHelper.registerBean(registry, key.concat(RedisComponentEnum.REDIS_TEMPLATE.getBeanNameSuffix()), AdvanceRedisTemplate.class, redisTemplateBeanDefinition);
                RedisComponentHelper.addComponent(RedisComponentEnum.REDIS_TEMPLATE, key, key.concat(RedisComponentEnum.REDIS_TEMPLATE.getBeanNameSuffix()),
                        isPrimary);

                // 注册stringRedisTemplate
                AbstractBeanDefinition stringRedisTemplateBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AdvanceStringRedisTemplate.class)
                        .addConstructorArgReference(factoryBeanDefinitionName)
                        .addConstructorArgValue(properties)
                        .addConstructorArgValue(key.concat(RedisComponentEnum.STRING_REDIS_TEMPLATE.getBeanNameSuffix()))
                        .getRawBeanDefinition();
//                stringRedisTemplateBeanDefinition.setPrimary(isPrimary);
                RedisComponentHelper.registerBean(registry, key.concat(RedisComponentEnum.STRING_REDIS_TEMPLATE.getBeanNameSuffix()), AdvanceStringRedisTemplate.class,
                        stringRedisTemplateBeanDefinition);
                RedisComponentHelper.addComponent(RedisComponentEnum.STRING_REDIS_TEMPLATE, key,
                        key.concat(RedisComponentEnum.STRING_REDIS_TEMPLATE.getBeanNameSuffix()), isPrimary);
            }
        }
    }

    /**
     * 注册响应式组件
     *
     * @param key
     * @param isPrimary
     */
    private void registerReactiveComponent(BeanDefinitionRegistry registry, String key, boolean isPrimary,
                                           String factoryBeanDefinitionName, RedisRegisterProperties properties) {

        JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
        RedisSerializationContext<Object, Object> serializationContext = RedisSerializationContext
                .newSerializationContext().key(jdkSerializer).value(jdkSerializer).hashKey(jdkSerializer)
                .hashValue(jdkSerializer).build();

        // 注册 ReactiveRedisTemplate
//        new AdvanceReactiveRedisTemplate(reactiveRedisConnectionFactory, serializationContext)
        AbstractBeanDefinition redisTemplateBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AdvanceReactiveRedisTemplate.class)
                .addConstructorArgReference(factoryBeanDefinitionName)
                .addConstructorArgValue(serializationContext)
                .addConstructorArgValue(properties)
                .addConstructorArgValue(key.concat(RedisComponentEnum.REACTIVE_REDIS_TEMPLATE.getBeanNameSuffix()))
                .getRawBeanDefinition();
        RedisComponentHelper.registerBean(registry, key.concat(RedisComponentEnum.REACTIVE_REDIS_TEMPLATE.getBeanNameSuffix()), AdvanceReactiveRedisTemplate.class,
                redisTemplateBeanDefinition);
        RedisComponentHelper.addComponent(RedisComponentEnum.REACTIVE_REDIS_TEMPLATE, key, key.concat(RedisComponentEnum.REDIS_TEMPLATE.getBeanNameSuffix()),
                isPrimary);

        // 注册 ReactiveStringRedisTemplate
        AbstractBeanDefinition stringRedisTemplateBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AdvanceReactiveStringRedisTemplate.class)
                .addConstructorArgReference(factoryBeanDefinitionName)
                .addConstructorArgValue(properties)
                .addConstructorArgValue(key.concat(RedisComponentEnum.REACTIVE_STRING_REDIS_TEMPLATE.getBeanNameSuffix()))
                .getRawBeanDefinition();
        RedisComponentHelper.registerBean(registry, key.concat(RedisComponentEnum.REACTIVE_STRING_REDIS_TEMPLATE.getBeanNameSuffix()), AdvanceReactiveStringRedisTemplate.class,
                stringRedisTemplateBeanDefinition);
        RedisComponentHelper.addComponent(RedisComponentEnum.REACTIVE_STRING_REDIS_TEMPLATE, key,
                key.concat(RedisComponentEnum.REACTIVE_STRING_REDIS_TEMPLATE.getBeanNameSuffix()), isPrimary);

        if (isPrimary) {
            // 兼容 org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration.reactiveRedisTemplate 注入流程的启动问题
            AbstractBeanDefinition primaryRedisTemplateBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AdvanceReactiveRedisTemplate.class)
                    .addConstructorArgReference(factoryBeanDefinitionName)
                    .addConstructorArgValue(serializationContext)
                    .addConstructorArgValue(properties)
                    .addConstructorArgValue(RedisComponentEnum.REACTIVE_REDIS_TEMPLATE.getPrimaryBeanName())
                    .getRawBeanDefinition();
            RedisComponentHelper.registerBean(registry, RedisComponentEnum.REACTIVE_REDIS_TEMPLATE.getPrimaryBeanName(), AdvanceReactiveRedisTemplate.class,
                    primaryRedisTemplateBeanDefinition);
            RedisComponentHelper.addComponent(RedisComponentEnum.REACTIVE_REDIS_TEMPLATE, key, RedisComponentEnum.REACTIVE_REDIS_TEMPLATE.getPrimaryBeanName(),
                    false);

            // 兼容 org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration.reactiveStringRedisTemplate 注入流程的启动问题
            AbstractBeanDefinition primaryStringRedisTemplateBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(
                            AdvanceReactiveStringRedisTemplate.class)
                    .addConstructorArgReference(factoryBeanDefinitionName)
                    .addConstructorArgValue(properties)
                    .addConstructorArgValue(RedisComponentEnum.REACTIVE_STRING_REDIS_TEMPLATE.getPrimaryBeanName())
                    .getRawBeanDefinition();
            RedisComponentHelper.registerBean(registry, RedisComponentEnum.REACTIVE_STRING_REDIS_TEMPLATE.getPrimaryBeanName(), AdvanceReactiveStringRedisTemplate.class,
                    primaryStringRedisTemplateBeanDefinition);
            RedisComponentHelper.addComponent(RedisComponentEnum.REACTIVE_STRING_REDIS_TEMPLATE, key,
                    RedisComponentEnum.REACTIVE_STRING_REDIS_TEMPLATE.getPrimaryBeanName(), false);

        }
    }

}

定义枚举信息

java 复制代码
/**
 * redis注册组件枚举
 * <p>@author chenmingjun </p>
 * <p>@date 2024-03-15 16:03</p>
 * <p>@since 1.0.0</p>
 */
@Getter
@AllArgsConstructor
public enum RedisComponentEnum {

    /**
     * 连接工厂
     */
    CONNECT_FACTORY("ConnectionFactory","redisConnectionFactory"),

    /**
     * 缓存管理器
     */
    CACHE_MANAGER("CacheManager","redisCacheManager"),

    /**
     * redisTemplate组件
     */
    REDIS_TEMPLATE("RedisTemplate","redisTemplate"),

    /**
     * stringRedisTemplate组件
     */
    STRING_REDIS_TEMPLATE("StringRedisTemplate","stringRedisTemplate"),

    /**
     * reactiveRedisTemplate组件
     */
    REACTIVE_REDIS_TEMPLATE("ReactiveRedisTemplate","reactiveRedisTemplate"),

    /**
     * reactiveStringRedisTemplate组件
     */
    REACTIVE_STRING_REDIS_TEMPLATE("ReactiveStringRedisTemplate","reactiveStringRedisTemplate"),

    /**
     * Redisson组件
     */
    REDISSON("RedissonClient","redisson"),
    ;

    /**
     * beanName后缀
     */
    private final String beanNameSuffix;

    /**
     * 默认注册BeanName
     */
    private final String primaryBeanName;

}

上述代码为注册 redis 的主要流程,用流程图整理下整体步骤

然后再看看注册的逻辑

注册 Bean 的 Helper

java 复制代码
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ClassUtils;

/**
 * redis 注册初始化上下文
 * <p>@author chenmingjun </p>
 * <p>@date 2024-03-15 16:00</p>
 * <p>@since 1.0.0</p>
 */
@Slf4j
public class RedisComponentHelper {

    /**
     * RedisComponentEnum, 组件类型 String,组件配置Key String,组件对应的Bean名称
     */
    private static final Map<RedisComponentEnum, Map<String, Set<String>>> REDIS_COMPNENT_MAP = new ConcurrentHashMap<>();
    /**
     * RedisComponentEnum, 组件类型 String,组件配置Key String,组件对应的PrimaryBean名称
     */
    private static final Map<RedisComponentEnum, Map<String, String>> REDIS_PRIMARY_COMPNENT_MAP = new ConcurrentHashMap<>();

    /**
     * 添加组件信息
     *
     * @param componentEnum
     * @param key
     * @param beanName
     */
    public static void addComponent(RedisComponentEnum componentEnum, String key, String beanName, boolean isPrimary) {
        Map<String, Set<String>> map = REDIS_COMPNENT_MAP.computeIfAbsent(componentEnum, k -> new ConcurrentHashMap<>());
        Set<String> beanNameSet = map.computeIfAbsent(key, k -> new HashSet<>());
        beanNameSet.add(beanName);
        if (isPrimary) {
            Map<String, String> primaryBeanName = REDIS_PRIMARY_COMPNENT_MAP.computeIfAbsent(componentEnum, k -> new ConcurrentHashMap<>());
            primaryBeanName.computeIfAbsent(key, k -> beanName);
        }
    }

    /**
     * 获取主Bean的名称
     *
     * @param componentEnum
     * @return
     */
    public static String getPrimaryBeanName(RedisComponentEnum componentEnum) {
        Map<String, String> map = REDIS_PRIMARY_COMPNENT_MAP.get(componentEnum);
        if (map == null) {
            return null;
        }
        for (Entry<String, String> entry : map.entrySet()) {
            return entry.getValue();
        }
        return null;
    }

    /**
     * 获取默认注册的Bean实例
     *
     * @param componentEnum
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getPrimaryBean(RedisComponentEnum componentEnum, Class<T> clazz) {
        Map<String, String> map = REDIS_PRIMARY_COMPNENT_MAP.get(componentEnum);
        if (map == null) {
            return null;
        }
        for (Entry<String, String> entry : map.entrySet()) {
            return getBean(componentEnum, entry.getKey(), entry.getValue(), clazz);
        }
        return null;
    }

    /**
     * 获取Key+后置拼接的Bean实例
     *
     * @param componentEnum
     * @param key
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getKeyConcatBean(RedisComponentEnum componentEnum, String key, Class<T> clazz) {
        return getBean(componentEnum, key, key.concat(componentEnum.getBeanNameSuffix()), clazz);
    }

    /**
     * 获取Bean实例
     *
     * @param componentEnum
     * @param key
     * @param beanName
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(RedisComponentEnum componentEnum, String key, String beanName, Class<T> clazz) {
        Map<String, Set<String>> map = REDIS_COMPNENT_MAP.get(componentEnum);
        if (map == null) {
            return null;
        }
        Set<String> beanNameSet = map.get(key);
        if (beanNameSet == null) {
            return null;
        }
        if (!beanNameSet.contains(beanName)) {
            return null;
        }
        ApplicationContext context = ApplicationContextHolder.context;
        if (context == null) {
            return null;
        }
        return context.getBean(beanName, clazz);
    }

    /**
     * BeanDefinition定义注册
     *
     * @param registry
     * @param beanName
     * @param clazz
     * @param rawBeanDefinition
     * @param <T>
     * @return
     */
    public static <T> void registerBean(BeanDefinitionRegistry registry, String beanName, Class<T> clazz, AbstractBeanDefinition rawBeanDefinition) {
        BeanDefinition beanDefinition = null;
        try {
            beanDefinition = registry.getBeanDefinition(beanName);
        } catch (NoSuchBeanDefinitionException e) {
            // ignore
        }
        if (beanDefinition != null) {
            String beanClassName = beanDefinition.getBeanClassName();
            if (beanClassName == null) {
                // 移除之前的beanDefinition,重新注册,数据源刷新
                registry.removeBeanDefinition(beanName);
            } else {
                Class<?> beanClass = null;
                try {
                    beanClass = ClassUtils.forName(beanClassName, ClassUtils.getDefaultClassLoader());
                } catch (ClassNotFoundException e) {
                    throw new BeanCreationException("ClassNotFound :" + beanClassName);
                }

                // 判断是否是同一个类或者父子类
                if (clazz.isAssignableFrom(beanClass) || beanClass.isAssignableFrom(clazz)) {
                    // 移除之前的beanDefinition,重新注册,数据源刷新
                    registry.removeBeanDefinition(beanName);
                } else {
                    throw new BeanCreationException("Duplicate beanName");
                }
            }
        }
        registry.registerBeanDefinition(beanName, rawBeanDefinition);
        log.info("=====>register beanName: {}, beanClass: {}", beanName, clazz.getCanonicalName());
    }
}

2. redisson 多数据源

参透了 redis-data 多数据源的注入逻辑,redisson 的注入就简单很多了 在注册 redisConnectFactory 的地方替换为注入 RedissonClient 即可

redisson注入的完整代码

java 复制代码
import java.io.IOException;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.redisson.spring.starter.RedissonProperties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Cluster;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.env.Environment;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder;
import org.springframework.util.ClassUtils;

/**
 * <p>@author chenmingjun </p>
 * <p>@date 2024-03-13 11:59</p>
 * <p>@since 1.0.0</p>
 */
@Slf4j
public class DynamicRedissonRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {


    private static final String PRIMARY_DEFAULT_KEY = "primary";

    private final Environment environment;

    public DynamicRedissonRegistryPostProcessor(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 默认的 redis 配置
        BindResult<RedisProperties> defaultResult = Binder.get(environment).bind("spring.redis", RedisProperties.class);
        // 默认的 redisson 配置
        BindResult<RedissonProperties> redissonResult = Binder.get(environment).bind("spring.redis.redisson", RedissonProperties.class);

        // 动态redis配置
        BindResult<DynamicRedisRegisterProperties> dynamicResult = Binder.get(environment)
            .bind(DynamicRedisRegisterProperties.PREFIX, DynamicRedisRegisterProperties.class);
        DynamicRedisRegisterProperties properties = null;
        if (!dynamicResult.isBound()) {
            CustomerConfig config = null;
            // 解析redisson配置
            if (redissonResult.isBound()) {
                try {
                    config = new CustomerConfig(Config.fromYAML(redissonResult.get().getConfig()));
                } catch (IOException e) {
                    try {
                        config = new CustomerConfig(Config.fromJSON(redissonResult.get().getConfig()));
                    } catch (IOException e1) {
                        throw new IllegalArgumentException("Can't parse config", e1);
                    }
                }
                if (config != null) {
                    properties = copyRedissonProperties(config, new DynamicRedisRegisterProperties());
                }
            } else if (defaultResult.isBound()) {
                // 兜底解析redis的配置
                properties = copyProperties(defaultResult.get(), new DynamicRedisRegisterProperties());
            }
        } else {
            properties = dynamicResult.get();
        }
        if (properties == null) {
            return;
        }
        log.info("==============register redisson=======================");
        String primary = properties.getPrimary();
        Map<String, RedisRegisterProperties> datasource = properties.getDatasource();
        if (datasource == null) {
            log.info("RedisRegisterProperties.datasource is null! ");
            return;
        }
        boolean multipleDataSource;
        if (datasource.size() > 1) {
            // 存在多个数据源的情况注册方式有所变化
            multipleDataSource = true;
        } else {
            multipleDataSource = false;
        }
        datasource.forEach((key, redisRegisterProperties) -> realRegister(registry, key, redisRegisterProperties, primary, multipleDataSource));
        log.info("==============register redisson=======================end");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    /**
     * 拷贝基本数据源的配置到动态数据源配置中
     *
     * @param config
     * @param properties
     * @return
     */
    private DynamicRedisRegisterProperties copyRedissonProperties(CustomerConfig config, DynamicRedisRegisterProperties properties) {
        Map<String, RedisRegisterProperties> datasource = properties.getDatasource() != null ? properties.getDatasource() : new ConcurrentHashMap<>();
        datasource.compute(PRIMARY_DEFAULT_KEY, (k, primary) -> {
            if (primary == null) {
                primary = new RedisRegisterProperties();
            }
            // 单机配置
            SingleServerConfig singleServerConfig = config.getSingleServerConfig();
            if (singleServerConfig != null) {
                primary.setDatabase(singleServerConfig.getDatabase());
                primary.setUrl(singleServerConfig.getAddress());
                primary.setUsername(singleServerConfig.getUsername());
                primary.setPassword(singleServerConfig.getPassword());
                if (singleServerConfig.getConnectTimeout() != 0) {
                    primary.setConnectTimeout(Duration.ofMillis(singleServerConfig.getConnectTimeout()));
                }
                if (singleServerConfig.getTimeout() != 0) {
                    primary.setTimeout(Duration.ofMillis(singleServerConfig.getTimeout()));
                }
                primary.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
                primary.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize());
            }
            // 集群配置
            ClusterServersConfig clusterServersConfig = config.getClusterServersConfig();
            if (clusterServersConfig != null) {
                Cluster cluster = new Cluster();
                cluster.setNodes(clusterServersConfig.getNodeAddresses());
                primary.setCluster(cluster);
                primary.setUsername(clusterServersConfig.getUsername());
                primary.setPassword(clusterServersConfig.getPassword());
                if (clusterServersConfig.getConnectTimeout() != 0) {
                    primary.setConnectTimeout(Duration.ofMillis(clusterServersConfig.getConnectTimeout()));
                }
                if (clusterServersConfig.getTimeout() != 0) {
                    primary.setTimeout(Duration.ofMillis(clusterServersConfig.getTimeout()));
                }
                primary.setConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize());
                primary.setConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize());
            }

            primary.setKeyPrefix(null);
            primary.setEnableKeyPrefix(false);
            primary.setThreads(config.getThreads());
            primary.setNettyThread(config.getNettyThreads());
            if (config.getTransportMode() != null) {
                primary.setTransportMode(config.getTransportMode().name());
            }
            return primary;
        });
        properties.setDatasource(datasource);
        properties.setPrimary(PRIMARY_DEFAULT_KEY);
        return properties;
    }

    /**
     * 拷贝基本数据源的配置到动态数据源配置中
     *
     * @param redisProperties
     * @param properties
     * @return
     */
    private DynamicRedisRegisterProperties copyProperties(RedisProperties redisProperties, DynamicRedisRegisterProperties properties) {
        Map<String, RedisRegisterProperties> datasource = properties.getDatasource() != null ? properties.getDatasource() : new ConcurrentHashMap<>();
        datasource.compute(PRIMARY_DEFAULT_KEY, (k, primary) -> {
            if (primary == null) {
                primary = new RedisRegisterProperties();
            }
            // 单机配置信息
            primary.setDatabase(redisProperties.getDatabase());
            primary.setUrl(redisProperties.getUrl());
            primary.setHost(redisProperties.getHost());
            primary.setPort(redisProperties.getPort());
            primary.setKeyPrefix(null);
            primary.setEnableKeyPrefix(false);
            if (redisProperties.getTimeout() != null) {
                primary.setTimeout(redisProperties.getTimeout());
            }
            if (redisProperties.getConnectTimeout() != null) {
                primary.setConnectTimeout(redisProperties.getConnectTimeout());
            }
            // 集群配置信息
            primary.setCluster(redisProperties.getCluster());
            // 基础账密信息
            primary.setUsername(redisProperties.getUsername());
            primary.setPassword(redisProperties.getPassword());
            return primary;
        });
        properties.setDatasource(datasource);
        properties.setPrimary(PRIMARY_DEFAULT_KEY);
        return properties;
    }

    /**
     * 实际注册逻辑
     *
     * @param key
     * @param redisRegisterProperties
     * @param primary
     */
    private void realRegister(BeanDefinitionRegistry registry, String key, RedisRegisterProperties redisRegisterProperties, String primary,
        boolean multipleDataSource) {
        // 单机配置
        String host = redisRegisterProperties.getHost();
        int port = redisRegisterProperties.getPort();
        // url配置
        String url = redisRegisterProperties.getUrl();
        if ((!StringUtils.equals("localhost", host) && port != 0) || StringUtils.isNotBlank(url)) {
            // 单机逻辑
            registerSingleMode(registry, key, redisRegisterProperties, primary, multipleDataSource);
        } else if (redisRegisterProperties.getCluster() != null) {
            // 集群逻辑
            registerClusterMode(registry, key, redisRegisterProperties, primary, multipleDataSource);
        }
    }

    /**
     * 单机模式注册
     *
     * @param registry
     * @param key
     * @param redisRegisterProperties
     * @param primary
     */
    private void registerSingleMode(BeanDefinitionRegistry registry, String key, RedisRegisterProperties redisRegisterProperties, String primary,
        boolean multipleDataSource) {
        String url = redisRegisterProperties.getUrl();
        boolean isPrimary = StringUtils.equals(primary, key);
        int database = redisRegisterProperties.getDatabase();

        Config config = new Config();
        SingleServerConfig singleServerConfig = config.useSingleServer();

        if (StringUtils.isNotBlank(url)) {
            ConnectionInfoHelper connectionInfo = ConnectionInfoHelper.parseUrl(url);
            singleServerConfig.setAddress(url);
            singleServerConfig.setUsername(redisRegisterProperties.getUsername() != null ?
                redisRegisterProperties.getUsername() : connectionInfo.getUsername());
            singleServerConfig.setPassword(redisRegisterProperties.getPassword() != null ?
                redisRegisterProperties.getPassword() : connectionInfo.getPassword());
        } else {
            ConnectionInfoHelper connectionInfo = new ConnectionInfoHelper(
                redisRegisterProperties.getHost(),
                redisRegisterProperties.getPort(),
                redisRegisterProperties.getUseSsl(),
                redisRegisterProperties.getUsername(),
                redisRegisterProperties.getPassword()
            );
            singleServerConfig.setAddress(connectionInfo.getUri().toString());
            singleServerConfig.setUsername(redisRegisterProperties.getUsername());
            singleServerConfig.setPassword(redisRegisterProperties.getPassword());
        }

        if (redisRegisterProperties.getConnectionPoolSize() != 0) {
            singleServerConfig.setConnectionPoolSize(redisRegisterProperties.getConnectionPoolSize());
        }
        if (redisRegisterProperties.getConnectionMinimumIdleSize() != 0) {
            singleServerConfig.setConnectionMinimumIdleSize(redisRegisterProperties.getConnectionMinimumIdleSize());
        }
        singleServerConfig.setDatabase(database);
        if (redisRegisterProperties.getConnectTimeout() != null) {
            singleServerConfig.setConnectTimeout((int) redisRegisterProperties.getConnectTimeout().toMillis());
        }
        if (redisRegisterProperties.getTimeout() != null) {
            singleServerConfig.setTimeout((int) redisRegisterProperties.getTimeout().toMillis());
        }
        // 定义Key前缀
        singleServerConfig.setNameMapper(new PrefixNameMapper(redisRegisterProperties.getKeyPrefix(), redisRegisterProperties.getEnableKeyPrefix()));

        // 注册连接信息
        registerClient(registry, key, isPrimary, config, redisRegisterProperties, multipleDataSource);
    }

    /**
     * 集群模式注册
     *
     * @param registry
     * @param key
     * @param redisRegisterProperties
     * @param primary
     */
    private void registerClusterMode(BeanDefinitionRegistry registry, String key, RedisRegisterProperties redisRegisterProperties, String primary,
        boolean multipleDataSource) {
        boolean isPrimary = StringUtils.equals(primary, key);
        Cluster cluster = redisRegisterProperties.getCluster();
        List<String> nodes = cluster.getNodes();
        if (CollectionUtils.isEmpty(nodes)) {
            throw new IllegalStateException("cluster nodes is empty");
        }
        Config config = new Config();
        ClusterServersConfig clusterServersConfig = config.useClusterServers();
        clusterServersConfig.setNodeAddresses(ConnectionInfoHelper.convertNodeList(nodes));
        clusterServersConfig.setUsername(redisRegisterProperties.getUsername());
        clusterServersConfig.setPassword(redisRegisterProperties.getPassword());
        if (redisRegisterProperties.getConnectionPoolSize() != 0) {
            clusterServersConfig.setMasterConnectionPoolSize(redisRegisterProperties.getConnectionPoolSize());
        }
        if (redisRegisterProperties.getConnectionMinimumIdleSize() != 0) {
            clusterServersConfig.setMasterConnectionMinimumIdleSize(redisRegisterProperties.getConnectionMinimumIdleSize());
        }

        if (redisRegisterProperties.getConnectTimeout() != null) {
            clusterServersConfig.setConnectTimeout((int) redisRegisterProperties.getConnectTimeout().toMillis());
        }
        if (redisRegisterProperties.getTimeout() != null) {
            clusterServersConfig.setTimeout((int) redisRegisterProperties.getTimeout().toMillis());
        }
        // 定义Key前缀
        clusterServersConfig.setNameMapper(new PrefixNameMapper(redisRegisterProperties.getKeyPrefix(), redisRegisterProperties.getEnableKeyPrefix()));

        // 注册 RedissonClient
        registerClient(registry, key, isPrimary, config, redisRegisterProperties, multipleDataSource);
    }

    /**
     * 注册连接信息
     *
     * @param registry
     * @param key
     * @param isPrimary
     * @param redissonConfig
     * @param properties
     */
    private void registerClient(BeanDefinitionRegistry registry, String key, boolean isPrimary, Config redissonConfig, RedisRegisterProperties properties,
        boolean multipleDataSource) {
        // 如果配置了redisson则注册 redissonConnectFactory
        String redisFactoryBeanName = key.concat(RedisComponentEnum.CONNECT_FACTORY.getBeanNameSuffix());
        AbstractBeanDefinition connectFactoryBeanDefinition = BeanDefinitionBuilder
            .genericBeanDefinition(RedissonConnectionFactory.class,
                () -> new RedissonConnectionFactory(Redisson.create(redissonConfig)))
            .getRawBeanDefinition();
        connectFactoryBeanDefinition.setPrimary(isPrimary);
        RedisComponentHelper.registerBean(registry, redisFactoryBeanName, PersistenceExceptionTranslator.class, connectFactoryBeanDefinition);
        RedisComponentHelper.addComponent(RedisComponentEnum.CONNECT_FACTORY, key, redisFactoryBeanName, isPrimary);

        if (multipleDataSource) {
            // 多数据源
            // 注册 RedissonClient
            String redissonClientBeanName = key.concat(RedisComponentEnum.REDISSON.getBeanNameSuffix());
            AbstractBeanDefinition rawBeanDefinition = BeanDefinitionBuilder
                .genericBeanDefinition(RedissonClient.class, () -> Redisson.create(redissonConfig))
                .setDestroyMethodName("shutdown")
                .getRawBeanDefinition();
            rawBeanDefinition.setPrimary(isPrimary);

            RedisComponentHelper.registerBean(registry, redissonClientBeanName, RedissonClient.class,
                rawBeanDefinition);
            RedisComponentHelper.addComponent(RedisComponentEnum.REDISSON, key, redissonClientBeanName, isPrimary);

            if (isPrimary) {
                // 兼容 org.redisson.spring.starter.RedissonAutoConfiguration.redisson 注入流程的启动问题
                AbstractBeanDefinition primaryBeanDefinition = BeanDefinitionBuilder
                    .genericBeanDefinition(RedissonClient.class, () -> Redisson.create(redissonConfig))
                    .setDestroyMethodName("shutdown")
                    .getRawBeanDefinition();

                RedisComponentHelper.registerBean(registry, RedisComponentEnum.REDISSON.getPrimaryBeanName(), RedissonClient.class, primaryBeanDefinition);
                RedisComponentHelper.addComponent(RedisComponentEnum.REDISSON, key, RedisComponentEnum.REDISSON.getPrimaryBeanName(), false);
            }
        } else {
            // 单数据源
            if (isPrimary) {
                // 兼容 org.redisson.spring.starter.RedissonAutoConfiguration.redisson 注入流程的启动问题
                AbstractBeanDefinition primaryBeanDefinition = BeanDefinitionBuilder
                    .genericBeanDefinition(RedissonClient.class, () -> Redisson.create(redissonConfig))
                    .setDestroyMethodName("shutdown")
                    .getRawBeanDefinition();

                RedisComponentHelper.registerBean(registry, RedisComponentEnum.REDISSON.getPrimaryBeanName(), RedissonClient.class, primaryBeanDefinition);
                RedisComponentHelper.addComponent(RedisComponentEnum.REDISSON, key, RedisComponentEnum.REDISSON.getPrimaryBeanName(), false);
            } else {
                String redissonClientBeanName = key.concat(RedisComponentEnum.REDISSON.getBeanNameSuffix());
                AbstractBeanDefinition rawBeanDefinition = BeanDefinitionBuilder
                    .genericBeanDefinition(RedissonClient.class, () -> Redisson.create(redissonConfig))
                    .setDestroyMethodName("shutdown")
                    .getRawBeanDefinition();
                rawBeanDefinition.setPrimary(isPrimary);

                RedisComponentHelper.registerBean(registry, redissonClientBeanName, RedissonClient.class,
                    rawBeanDefinition);
                RedisComponentHelper.addComponent(RedisComponentEnum.REDISSON, key, redissonClientBeanName, isPrimary);

            }
        }
    }

}

Q&A

RedisComponentHelper#registerBean中删除BeanDefinition 的用意

该类中registerBean有一段代码逻辑需要去关注的一下,如果不需要可按需调整

这段删除 Bean 定义的逻辑可以按需调整,此处是为了兼容在不排除redis 或者 redisson 的默认 AutoConfiguration 类的情况下,会注入官方定义默认的默认的 BeanDefinition

只有在 Bean 定义阶段删除已经注入的 BeanDefinition,进而重新注入自定义实现的 BeanDefinition,才能够实现替换官方原有的 Bean 实例

为何注入 RedisConnectFactory 和 RedissonConnectFactory 中注入 Bean 的类型是 PersistenceExceptionTranslator.class

1、首先PersistenceExceptionTranslator 是接口类,不管是 LettuceConnectionFactory 还是RedissonConnectionFactory 都实现的该类,使用该接口类相对规范和统一

2、其次在一个应用中如果有两个 ConnectFactory 会造成一定的编码歧义,以及实际执行语句不正确的问题为什么这么说呢 之前遇到过一个问题

java 复制代码
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TvApplication.class)
public class Mains {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Test
    public void test() {
        redisTemplate.opsForZSet().add("sadf", "1", 1);
    }
}

报错信息是

java 复制代码
java.lang.StackOverflowError
	at org.springframework.data.redis.connection.DefaultedRedisConnection.zAdd(DefaultedRedisConnection.java:863)
	at org.springframework.data.redis.connection.DefaultedRedisConnection.zAdd(DefaultedRedisConnection.java:863)
	at org.springframework.data.redis.connection.DefaultedRedisConnection.zAdd(DefaultedRedisConnection.java:863)
 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。省略部分堆栈
    at org.springframework.data.redis.connection.DefaultedRedisConnection.zAdd(DefaultedRedisConnection.java:863)
	at org.springframework.data.redis.connection.DefaultedRedisConnection.zAdd(DefaultedRedisConnection.java:863)

网上有些博客也有提及:

redisson与sping boot版本不兼容的问题

这里说的是版本不兼容问题,实际 debug 看的时候确实是的

思考了下为啥 redisTemplate 会和 redisson 的版本有关,于是点击进入他的自动注入逻辑看看,发现他在注入的时候

默认注入的是 redissonConnectFactory,在生成 redisTemplate 的时候引入的连接工厂也是 redisson 的,所以版本不兼容的情况就会导致上面的问题。除了版本不兼容的问题,在执行特定脚本的返回值也是会有所差别的

例如在执行这段 lua 脚本的时候,不同连接工厂的表现是不一样的,redis 的是null,redisson 是空数组

综上所述,需要确保应用中只有一个连接工厂,否则会导致最终执行的结果和想要的结果不一致的问题。

相关推荐
技术小泽7 分钟前
代码思想之快慢路径
java·开发语言·spring boot·后端·设计规范
*长铗归来*28 分钟前
ASP.NET Core Web API Hangfire
后端·c#·asp.net·.netcore
江东飞过28 分钟前
.net core 的文件操作
开发语言·后端·golang
久久不觉29 分钟前
.net core 的字符串处理
开发语言·后端·golang
久久不觉30 分钟前
.net core 的软件开发工具
开发语言·后端·golang
司马相楠31 分钟前
.net core 的软件开发技能
开发语言·后端·golang
司马相楠32 分钟前
.net core 的数据库编程
开发语言·后端·golang
龙晓飞度33 分钟前
.net core 的函数实现
开发语言·后端·golang
陈玉玉儿33 分钟前
.net core 的多线程编程
开发语言·后端·golang
开心工作室_kaic1 小时前
springboot504基于Springboot网上蛋糕售卖店管理系统的设计与实现(论文+源码)_kaic
java·spring boot·后端