theme: vue-pro highlight: atom-one-dark
背景
开发自用工程,清理线上redis集群内指定工程缓存。遂搜索,发现工具 fastdep。
使用方式
标准配置文件
fastdep:
redis:
redis1: #连接名称 product
database: 0
host: redis1
port: 6379
password: xxx
lettuce: #下面为连接池的补充设置
shutdown-timeout: 100 # 关闭超时时间
pool:
max-active: 10 # 连接池最大连接数(使用负值表示没有限制)
max-idle: 3 # 连接池中的最大空闲连接
max-wait: 30 # 连接池最大阻塞等待时间(使用负值表示没有限制)
min-idle: 0 # 连接池中的最小空闲连接
redis2: #连接名称
host: reids2
port: 6379
database: 0
password: xxxx
lettuce: #下面为连接池的补充设置
shutdown-timeout: 100 # 关闭超时时间
pool:
max-active: 10 # 连接池最大连接数(使用负值表示没有限制)
max-idle: 3 # 连接池中的最大空闲连接
max-wait: 30 # 连接池最大阻塞等待时间(使用负值表示没有限制)
min-idle: 0 # 连接池中的最小空闲连接
工具类
@Component
public class RedisUtil {
@Autowired
private StringRedisTemplate redis1StringRedisTemplate;
@Autowired
private StringRedisTemplate redis2StringRedisTemplate;
@Autowired
private StringRedisTemplate redis3StringRedisTemplate;
@Autowired
private RedisTemplate redis2RedisTemplate;
@Autowired
private RedisTemplate redis1RedisTemplate;
@Autowired
private RedisTemplate redis3RedisTemplate;
public RedisTemplate redisTemplate(String name) {
RedisTemplate redisTemplate;
switch (name) {
case "redis2":
redisTemplate = redis2RedisTemplate;
break;
case "redis3":
redisTemplate = redis3RedisTemplate;
break;
default:
redisTemplate = redis1RedisTemplate;
break;
}
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(stringRedisSerializer);
return redisTemplate;
}
public StringRedisTemplate stringRedisTemplate(String name) {
StringRedisTemplate stringRedisTemplate;
switch (name) {
case "redis2":
stringRedisTemplate = redis2StringRedisTemplate;
break;
case "redis3":
stringRedisTemplate = redis3StringRedisTemplate;
break;
default:
stringRedisTemplate = redis1StringRedisTemplate;
break;
}
stringRedisTemplate.setEnableTransactionSupport(true);
return stringRedisTemplate;
}
}
原理介绍
回顾下Spring Boot自动配置流程
- 创建一个 Spring Boot 应用,并在主类上使用
@SpringBootApplication
注解。 -
@SpringBootApplication
包含@EnableAutoConfiguration
,告诉 Spring Boot 启用自动配置。 - Spring Boot 在启动时查找
META-INF/spring.factories
文件,并加载所有列出的自动配置类。 - 自动配置类中的条件注解 (
@Conditional
...)被评估,条件满足相关的 bean 被创建。 - 可以提供了自己的配置,覆盖自动配置类。
- 程序启动,所有配置好的 bean 可以被使用。
spring.factories 定义自动配置类
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.louislivi.fastdep.redis.FastDepRedisAutoConfiguration
FastDepRedisAutoConfiguration 自动配置类
// 表示这个类是一个配置类,包含了一个或多个 @Bean 方法,这些方法会被 Spring 容器处理,以生成 Spring 应用程序上下文中的 bean 定义
@Configuration
/**
* 使用 @ConfigurationProperties 注解的类生效。
* @EnableConfigurationProperties 相当于把使用 @ConfigurationProperties 的类进行了一次注入。
* 可以将配置文件中的属性绑定到 FastDepRedis 类的实例上。
*/
@EnableConfigurationProperties({FastDepRedis.class})
// FastDepRedisAutoConfiguration 应该在 RedisAutoConfiguration 之前被加载。
// RedisAutoConfiguration 是 Spring Boot 提供的默认 Redis 自动配置类,确保 FastDepRedisAutoConfiguration 优先于默认配置执行,从而覆盖或自定义默认配置。
@AutoConfigureBefore({RedisAutoConfiguration.class})
// 注册 FastDepRedisRegister 类到 Spring 容器
@Import(FastDepRedisRegister.class)
public class FastDepRedisAutoConfiguration {
}
配置类
// 只有在 application.properties 或 application.yml 配置文件中存在 fastdep.redis 属性时,这个配置类才会被加载和解析
@ConditionalOnProperty("fastdep.redis")
// 告诉 Spring Boot 这个类的属性应该从配置文件中 fastdep 前缀下的部分读取
@ConfigurationProperties("fastdep")
public class FastDepRedis {
/**
* key-字符串,value-RedisProperties 对象。用于存储多个 Redis 实例的配置信息,其中每个实例都有一个唯一的名称作为键。
*/
private Map<String, RedisProperties> redis;
public Map<String, RedisProperties> getRedis() {
return redis;
}
public void setRedis(Map<String, RedisProperties> redis) {
this.redis = redis;
}
}
自动配置并注册 Redis 数据源
public class FastDepRedisRegister implements EnvironmentAware, ImportBeanDefinitionRegistrar {
/**
* 日志记录器,用于记录日志信息。
*/
private static final Logger logger = LoggerFactory.getLogger(com.louislivi.fastdep.redis.FastDepRedisRegister.class);
/**
* 存储已注册的 Bean 实例
*/
private static Map<String, Object> registerBean = new ConcurrentHashMap<>();
/**
* 访问应用程序的配置属性
*/
private Environment env;
/**
* 通常与 @ConfigurationProperties 注解(标记一个类的属性应该被绑定到配置属性上)一起使用,Binder 可以直接在代码中使用,运行时动态地绑定和解析配置属性。
*/
private Binder binder;
/**
* 实现了 ImportBeanDefinitionRegistrar 接口中的 registerBeanDefinitions 方法,用于向 Spring 容器中注册与 Redis 相关的 Bean 定义
*
* @param annotationMetadata annotationMetadata
* @param beanDefinitionRegistry beanDefinitionRegistry
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// get all redis config(从配置文件中读取所有的 Redis 配置信息)
Map<String, Map> multipleRedis;
try {
multipleRedis = binder.bind("fastdep.redis", Map.class).get();
} catch (NoSuchElementException e) {
logger.error("Failed to configure fastDep redis: 'fastdep.redis' attribute is not specified and no embedded redis could be configured.");
return;
}
boolean onPrimary = true;
// 遍历这些配置信息,为每个 Redis 实例创建和配置 RedisStandaloneConfiguration、GenericObjectPoolConfig 和 LettuceConnectionFactory 对象。
for (String key : multipleRedis.keySet()) {
Map map = binder.bind("fastdep.redis." + key, Map.class).get();
// RedisStandaloneConfiguration(Redis客户端配置信息)
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName((String) map.get("host"));
configuration.setPort((Integer) map.get("port"));
configuration.setDatabase((Integer) map.get("database"));
String password = (String) map.get("password");
if (!StringUtils.isEmpty(password)) {
RedisPassword redisPassword = RedisPassword.of(password);
configuration.setPassword(redisPassword);
}
// GenericObjectPoolConfig(配置Lettuce或Jedis连接池信息)
GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
try {
RedisProperties.Pool pool = binder.bind("fastdep.redis." + key + ".lettuce.pool", RedisProperties.Pool.class).get();
genericObjectPoolConfig.setMaxIdle(pool.getMaxIdle());
genericObjectPoolConfig.setMaxTotal(pool.getMaxActive());
genericObjectPoolConfig.setMinIdle(pool.getMinIdle());
if (pool.getMaxWait() != null) {
genericObjectPoolConfig.setMaxWaitMillis(pool.getMaxWait().toMillis());
}
} catch (NoSuchElementException ignore) {
}
// LettuceConnectionFactory(Lettuce 连接池的实现,提供了对 Redis 客户端的统一管理)
Supplier<LettuceConnectionFactory> lettuceConnectionFactorySupplier = () -> {
LettuceConnectionFactory factory = (LettuceConnectionFactory) registerBean.get(key + "LettuceConnectionFactory");
if (factory != null) {
return factory;
}
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
try {
Duration shutdownTimeout = binder.bind("fastdep.redis." + key + ".shutdown-timeout", Duration.class).get();
if (shutdownTimeout != null) {
builder.shutdownTimeout(shutdownTimeout);
}
} catch (NoSuchElementException ignore) {
}
LettuceClientConfiguration clientConfiguration = builder.poolConfig(genericObjectPoolConfig).build();
factory = new LettuceConnectionFactory(configuration, clientConfiguration);
registerBean.put(key + "LettuceConnectionFactory", factory);
return factory;
};
LettuceConnectionFactory lettuceConnectionFactory = lettuceConnectionFactorySupplier.get();
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(LettuceConnectionFactory.class, lettuceConnectionFactorySupplier);
// 使用 BeanDefinitionBuilder 创建 LettuceConnectionFactory、StringRedisTemplate 和 RedisTemplate 的 bean 定义,并将它们注册到 Spring 容器中。
AbstractBeanDefinition factoryBean = builder.getRawBeanDefinition();
factoryBean.setPrimary(onPrimary);
beanDefinitionRegistry.registerBeanDefinition(key + "LettuceConnectionFactory", factoryBean);
// 注册 StringRedisTemplate 和 RedisTemplate Bean:
// StringRedisTemplate
GenericBeanDefinition stringRedisTemplate = new GenericBeanDefinition();
stringRedisTemplate.setBeanClass(StringRedisTemplate.class);
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(0, lettuceConnectionFactory);
stringRedisTemplate.setConstructorArgumentValues(constructorArgumentValues);
stringRedisTemplate.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);
beanDefinitionRegistry.registerBeanDefinition(key + "StringRedisTemplate", stringRedisTemplate);
// RedisTemplate
GenericBeanDefinition redisTemplate = new GenericBeanDefinition();
redisTemplate.setBeanClass(RedisTemplate.class);
redisTemplate.getPropertyValues().add("connectionFactory", lettuceConnectionFactory);
redisTemplate.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);
beanDefinitionRegistry.registerBeanDefinition(key + "RedisTemplate", redisTemplate);
logger.info("Registration redis ({}) !", key);
if (onPrimary) {
onPrimary = false;
}
}
logger.info("Registration redis completed !");
}
/**
* 实现 EnvironmentAware 接口中的 setEnvironment 方法。
* 接收 Environment 对象将其赋值给类的成员变量 env 并创建了一个 Binder 对象,用于后续的配置属性绑定
*/
@Override
public void setEnvironment(Environment environment) {
this.env = environment;
// bing binder
binder = Binder.get(this.env);
}
}
总结
主要原理包含:
- 自动配置:利用 Spring Boot 自动配置特性,在类路径上检测到特定的依赖,自动配置相关的 beans。
-
配置属性解析:读取
application.yaml
或application.properties
文件中的配置属性,并根据这些属性创建配置 Redis 客户端实例。 -
Bean 的创建和注入:根据配置文件中定义的数据源,
FastDepRedis
创建多个RedisTemplate
或StringRedisTemplate
实例,将它们注册为 Spring 应用程序上下文中的 beans。 -
条件注解:使用 Spring 的条件注解(如
@ConditionalOnClass
和@ConditionalOnProperty
)确保只有在满足特定条件时才创建和配置 beans。