掘金 后端 ( ) • 2024-04-22 14:22

theme: condensed-night-purple

背景

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

实现的效果演示

配置信息 多数据源

# 动态数据源
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

兼容原有单数据源

# 单机
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

完整配置文件属性

@Getter
@Setter
public class RedisRegisterProperties {

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

    /**
     * 连接 URL,可覆盖host、port和password的配置。用户如果没有可以忽略。 示例:redis://user:[email protected]: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 image.png

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

定义配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\  
com.base.redis.DynamicRedisConfig
@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


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);

        }
    }

}

定义枚举信息

/**
 * 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 的主要流程,用流程图整理下整体步骤

redis 实例注册流程.png

然后再看看注册的逻辑

注册 Bean 的 Helper


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 即可

image.png

image.png

redisson注入的完整代码


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有一段代码逻辑需要去关注的一下,如果不需要可按需调整

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

image.png

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

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

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

image.png

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

@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.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版本不兼容的问题

image.png

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

image.png

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

img_v3_029c_e52bc2b6-c123-45fd-9908-8f022c005b9g.jpg

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

img_v3_02a6_a3c8937b-4133-4ee9-8b29-03fc219d5e7g.jpg 例如在执行这段 lua 脚本的时候,不同连接工厂的表现是不一样的,redis 的是null,redisson 是空数组

img_v3_02a6_7ff4590e-ece3-4c0e-a415-0809e88792cg.jpg

img_v3_02a6_29b9fbb9-ead7-4995-8e09-4319ff0fa34g.jpg

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