背景
由于业务场景需要,在一个项目中可能存在多个 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)
网上有些博客也有提及:
这里说的是版本不兼容问题,实际 debug 看的时候确实是的
思考了下为啥 redisTemplate 会和 redisson 的版本有关,于是点击进入他的自动注入逻辑看看,发现他在注入的时候
默认注入的是 redissonConnectFactory,在生成 redisTemplate 的时候引入的连接工厂也是 redisson 的,所以版本不兼容的情况就会导致上面的问题。除了版本不兼容的问题,在执行特定脚本的返回值也是会有所差别的
例如在执行这段 lua 脚本的时候,不同连接工厂的表现是不一样的,redis 的是null,redisson 是空数组
综上所述,需要确保应用中只有一个连接工厂,否则会导致最终执行的结果和想要的结果不一致的问题。