前言
當(dāng)我們用srping cloud config做配置中心的時(shí)候由于缺少動態(tài)修改配置的功能,所以無法做到即使的對一些代碼流程做控制,每次修改配置都需要重啟服務(wù)從git拉取新的配置,這樣非常不方便所以我們需要一個輕量級的配置開關(guān)峡蟋,需要能做到不重啟服務(wù)就可以動態(tài)的修改配置苞也。
設(shè)計(jì)
基于目前不是很復(fù)雜的場景,我的想法是設(shè)計(jì)一個utils页屠。各種初始化的配置,直接由靜態(tài)代碼塊初始化到一個map即可勺阐,首次調(diào)用時(shí)我們把配置從java內(nèi)存加載到redis卷中,之后的調(diào)用只要redis有值就從redis獲取,如果redis沒值就從靜態(tài)map中獲取渊抽。之后只需要修改redis里面的值就可以做到動態(tài)的修改配置了蟆豫。
編碼
實(shí)現(xiàn)@Cacheable注解
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
@Value("#{${cachetime.config}}")
private Map<String, Integer> cacheTimeConfig;
@Bean
//當(dāng)容器不存在RedisTemplate時(shí),自定義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
//當(dāng)容器不存在StringRedisTemplate時(shí)懒闷,自定義StringRedisTemplate
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
/**
* 設(shè)置springCache的過期時(shí)間
*/
@Bean
// 定義一個方法十减,用于創(chuàng)建RedisCacheManager實(shí)例栈幸,參數(shù)為RedisConnectionFactory對象
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
// 創(chuàng)建一個GenericFastJsonRedisSerializer實(shí)例,用于序列化和反序列化緩存數(shù)據(jù)到JSON格式
GenericFastJsonRedisSerializer jsonRedisSerializer = new GenericFastJsonRedisSerializer();
// 創(chuàng)建一個RedisSerializationContext.SerializationPair對象帮辟,指定使用jsonRedisSerializer作為值的序列化器
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer);
// 獲取RedisCacheConfiguration的默認(rèn)配置速址,并進(jìn)行設(shè)置
RedisCacheConfiguration defaultCacheConfig =
RedisCacheConfiguration.defaultCacheConfig()
// 禁止緩存空值
.disableCachingNullValues()
// 設(shè)置緩存值的序列化方式為之前定義的pair
.serializeValuesWith(pair);
// 創(chuàng)建一個非鎖定Redis緩存寫入器,與給定的RedisConnectionFactory關(guān)聯(lián)
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
// 使用ImmutableSet.Builder構(gòu)建器初始化一個不可變集合由驹,用于存儲自定義緩存名稱
ImmutableSet.Builder<String> cacheNames = ImmutableSet.builder();
// 使用ImmutableMap.Builder構(gòu)建器初始化一個不可變映射芍锚,用于存儲每個緩存名稱對應(yīng)的配置
ImmutableMap.Builder<String, RedisCacheConfiguration> cacheConfig = ImmutableMap.builder();
// 遍歷從properties文件中注入的cacheTimeConfig,該map存儲了各個緩存名稱及其過期時(shí)間(以秒為單位)
for (Map.Entry<String, Integer> entry : cacheTimeConfig.entrySet()) {
// 將當(dāng)前緩存名稱添加到cacheNames集合中
cacheNames.add(entry.getKey());
// 根據(jù)entry.getValue()(即緩存過期時(shí)間)設(shè)置defaultCacheConfig的過期時(shí)間蔓榄,并將其放入cacheConfig映射中
cacheConfig.put(entry.getKey(), defaultCacheConfig.entryTtl(Duration.ofSeconds(entry.getValue())));
}
// 使用RedisCacheManager的builder模式創(chuàng)建RedisCacheManager實(shí)例
return RedisCacheManager.builder(redisCacheWriter)
// 設(shè)置默認(rèn)的緩存配置
.cacheDefaults(defaultCacheConfig)
// 設(shè)置初始緩存名稱列表
.initialCacheNames(cacheNames.build())
// 設(shè)置每個緩存的特定配置
.withInitialCacheConfigurations(cacheConfig.build())
// 構(gòu)建并返回最終的RedisCacheManager實(shí)例
.build();
}
}
如果我們需要給key配置失效時(shí)間并炮,需要在yml中寫入就好.如果希望key不失效,不寫配置就好甥郑。
cachetime:
config: "{customerItemlist:3600,test:1200}"
實(shí)現(xiàn)工具類
@Slf4j
@Configuration
public class SwitchUtils {
/**
* 海報(bào)文案
*/
public static String Default_Poster_KEY = "DefaultUserShowCashKEY";
public static Boolean Default_Poster_VAL = false;
/**
* 不可分享的優(yōu)惠券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);
// 基礎(chǔ)類型轉(zhuǎn)換
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);
后續(xù)設(shè)計(jì)
如果知識開發(fā)同事需要的話逃魄,這樣的設(shè)計(jì)已經(jīng)足夠使用了,也可以把k,v存儲在db或者配置文件中澜搅,如果需要運(yùn)營同學(xué)使用伍俘,還需要提供更新緩存的接口。同時(shí)注意風(fēng)控勉躺。