前言
当我们用srping cloud config做配置中心的时候由于缺少动态修改配置的功能,所以无法做到即使的对一些代码流程做控制,每次修改配置都需要重启服务从git拉取新的配置,这样非常不方便所以我们需要一个轻量级的配置开关,需要能做到不重启服务就可以动态的修改配置。
设计
基于目前不是很复杂的场景,我的想法是设计一个utils。各种初始化的配置,直接由静态代码块初始化到一个map即可,首次调用时我们把配置从java内存加载到redis,之后的调用只要redis有值就从redis获取,如果redis没值就从静态map中获取。之后只需要修改redis里面的值就可以做到动态的修改配置了。
编码
实现@Cacheable注解
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
@Value("#{${cachetime.config}}")
private Map<String, Integer> cacheTimeConfig;
@Bean
//当容器不存在RedisTemplate时,自定义RedisTemplate
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
//使用fastjson序列化
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
// value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
//当容器不存在StringRedisTemplate时,自定义StringRedisTemplate
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
/**
* 设置springCache的过期时间
*/
@Bean
// 定义一个方法,用于创建RedisCacheManager实例,参数为RedisConnectionFactory对象
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
// 创建一个GenericFastJsonRedisSerializer实例,用于序列化和反序列化缓存数据到JSON格式
GenericFastJsonRedisSerializer jsonRedisSerializer = new GenericFastJsonRedisSerializer();
// 创建一个RedisSerializationContext.SerializationPair对象,指定使用jsonRedisSerializer作为值的序列化器
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer);
// 获取RedisCacheConfiguration的默认配置,并进行设置
RedisCacheConfiguration defaultCacheConfig =
RedisCacheConfiguration.defaultCacheConfig()
// 禁止缓存空值
.disableCachingNullValues()
// 设置缓存值的序列化方式为之前定义的pair
.serializeValuesWith(pair);
// 创建一个非锁定Redis缓存写入器,与给定的RedisConnectionFactory关联
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
// 使用ImmutableSet.Builder构建器初始化一个不可变集合,用于存储自定义缓存名称
ImmutableSet.Builder<String> cacheNames = ImmutableSet.builder();
// 使用ImmutableMap.Builder构建器初始化一个不可变映射,用于存储每个缓存名称对应的配置
ImmutableMap.Builder<String, RedisCacheConfiguration> cacheConfig = ImmutableMap.builder();
// 遍历从properties文件中注入的cacheTimeConfig,该map存储了各个缓存名称及其过期时间(以秒为单位)
for (Map.Entry<String, Integer> entry : cacheTimeConfig.entrySet()) {
// 将当前缓存名称添加到cacheNames集合中
cacheNames.add(entry.getKey());
// 根据entry.getValue()(即缓存过期时间)设置defaultCacheConfig的过期时间,并将其放入cacheConfig映射中
cacheConfig.put(entry.getKey(), defaultCacheConfig.entryTtl(Duration.ofSeconds(entry.getValue())));
}
// 使用RedisCacheManager的builder模式创建RedisCacheManager实例
return RedisCacheManager.builder(redisCacheWriter)
// 设置默认的缓存配置
.cacheDefaults(defaultCacheConfig)
// 设置初始缓存名称列表
.initialCacheNames(cacheNames.build())
// 设置每个缓存的特定配置
.withInitialCacheConfigurations(cacheConfig.build())
// 构建并返回最终的RedisCacheManager实例
.build();
}
}
如果我们需要给key配置失效时间,需要在yml中写入就好.如果希望key不失效,不写配置就好。
cachetime:
config: "{customerItemlist:3600,test:1200}"
实现工具类
@Slf4j
@Configuration
public class SwitchUtils {
/**
* 海报文案
*/
public static String Default_Poster_KEY = "DefaultUserShowCashKEY";
public static Boolean Default_Poster_VAL = false;
/**
* 不可分享的优惠券id
*/
public static String DefaultCanNotGive_KEY = "DefaultCanNotGive";
public static String DefaultCanNotGive_VAL = "[10155,10165,10167]";
public static Map<String, Object> switchMap = new HashMap<>();
static {
switchMap.put(Default_Poster_KEY, Default_Poster_VAL);
switchMap.put(DefaultCanNotGive_KEY, DefaultCanNotGive_VAL);
}
/**
* 单值类型
*/
@Cacheable(cacheNames = "SWITCH", key = "#switchKey + '_' + #clazz.getSimpleName()", unless = "#result == null")
public <T> T getSwitch(String switchKey, Class<T> clazz) {
Object value = switchMap.get(switchKey);
// 基础类型转换
if (clazz.isAssignableFrom(Boolean.class)) {
return clazz.cast(value);
} else if (clazz.isAssignableFrom(String.class)) {
return clazz.cast(value);
} else if (clazz.isAssignableFrom(Long.class)) {
return clazz.cast(value);
} else if (clazz.isAssignableFrom(Double.class)) {
return clazz.cast(value);
}
// 其他未知类型
throw new IllegalArgumentException("Unsupported type: " + clazz.getName());
}
@Cacheable(cacheNames = "SWITCH", key = "#switchKey", unless = "#result == null")
public <T> List<T> getStringListSwitch(String switchKey, Class<T> clazz) {
String defaultValue = (String) switchMap.get(switchKey);
if (Objects.isNull(defaultValue)) {
return null;
}
return JSONObject.parseArray(defaultValue, clazz);
}
}
测试
List<Integer> stringListSwitch = switchUtils.getStringListSwitch(SwitchUtils.DefaultCanNotGive_KEY, Integer.class);
System.out.println(stringListSwitch);
Boolean val = switchUtils.getSwitch(SwitchUtils.Default_Poster_KEY, Boolean.class);
System.out.println(val);
后续设计
如果知识开发同事需要的话,这样的设计已经足够使用了,也可以把k,v存储在db或者配置文件中,如果需要运营同学使用,还需要提供更新缓存的接口。同时注意风控。