添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

SpringBoot项目配置信息大多使用@Value注解或者@ConfigurationProperties注解读取配置信息,线上项目经常需要对某些配置进行调整,如果每次都需要修改配置文件再重新发布服务,难免会导致服务中断。

尤其是在分布式系统中多个服务节点都需要修改配置文件的场景,基于此配置中心也应运而生。

如果我们的项目使用了SpringCloud,那么可选的配置中心有很多,比如Nacos、spring-cloud-starter-config、Apollo等,这些配置中心都需要借助SpringCloud架构才能实现配置刷新。

这里我们的应用没有集成SpringCloud,也不想因为配置中心而让应用架构变重,所以需要基于SpringBoot基础实现一个轻量级的配置动态刷新功能。

远程配置中心

对配置资源进行管理,所有应用连接配置中心读取配置信息,可选用的配置中心包括:

1、配置中心中间件,例如:Nacos;

2、数据库存储;

3、Git仓库;

4、文件系统;

5、其它具备持久化存储及访问功能的中间件。

说点听得懂的实现原理

想要实现配置动态刷新可以从Spring的Bean初始化和属性值注入原理入手,这里我们跳过原理分析阶段,感兴趣的同学自行百度,建议大家可以直接看下Spring的PropertyPlaceholderAutoConfiguration类。

刷新配置信息仅需简单两步:

1、刷新Spring的Environment环境变量;

2、刷新Spring托管的Bean实例的属性值;

刷新Environment

* 刷新环境变量 * @param properties private MutablePropertySources refreshEnvironment(Properties properties) { Map <String, Object> props = new HashMap<> (); properties.stringPropertyNames().stream().forEach(key -> props.put(key, properties.getProperty(key)) // 获取spring的environment MutablePropertySources mutablePropertySources = environment.getPropertySources(); // 添加远程配置信息 mutablePropertySources.addFirst( new MapPropertySource("remoteConfig" , props)); return mutablePropertySources;

刷新Bean实例

需要注意一下几点:

1、我们不用自己解析@Value的value,通过Spring提供的PropertySourcesPropertyResolver.resolveRequiredPlaceholders即可从环境变量中获取对应的属性值;

2、@Value可以使用EL表达式,注入的属性类型可以是String、List等对象,通过Spring提供的SpelExpressionParser类实现EL表达式解析和运算取值。

* 刷新Bean实例的属性值 *
@param bean * @param propertyResolver private void refreshBean(Object bean, ConfigurablePropertyResolver propertyResolver) { // 定义EL表达式解释器 SpelExpressionParser spelExpressionParser; spelExpressionParser = new SpelExpressionParser(); TemplateParserContext templateParserContext; templateParserContext = new TemplateParserContext(); String keyResolver, valueResolver = null ; Object parserValue; // 遍历Bean实例所有属性 for (Field field : bean.getClass().getDeclaredFields()) { // 判断field是否含有@Value注解 if (field.isAnnotationPresent(Value. class )) { // 读取Value注解占位符 keyResolver = field.getAnnotation(Value. class ).value(); try { // 读取属性值 valueResolver = propertyResolver.resolveRequiredPlaceholders(keyResolver); // EL表达式解析 // 兼容形如:@Value("#{'${url}'.split(',')}")含有EL表达式的情况 parserValue = spelExpressionParser.parseExpression(valueResolver, templateParserContext).getValue(field.getType()); } catch (IllegalArgumentException e) { log.warn( "{}" , e.getMessage()); continue ; // 判断配置项是否存在 if (Objects.nonNull(valueResolver)) { field.setAccessible( true ); try { field.set(bean, parserValue); continue ; } catch (IllegalAccessException e) { log.error( "{}刷新属性值出错, bean: [{}], field: [{}], value: [{}]" , TAG, bean.getClass().getName(), field.getName(), valueResolver);