一. 背景
最近在重构自己负责产品的脚手架,本文介绍了我在Spring中集成Disconf客户端并实现配置动态获取的做法。
二. 配置
2.1 maven
<dependency>
<groupId>com.baidu.disconf</groupId>
<artifactId>disconf-client</artifactId>
<version>2.6.36</version>
</dependency>
2.2 Disconf 配置类
Disconf配置类需要提供最核心的几个类分别是:
1. DisconfMgrBean
第一次扫描。下载远端disconf中的配置文件、扫描本地指定路径的静态配置类以及配置文件信息,最终将所有的数据整合并存入本地仓库(其实就是一个Map)中。
2. DisconfMgrBeanSecond
第二次扫描。扫描配置项或配置文件的回调函数,获取并处理指定的配置文件实例,接着处理配置文件中的所有文件项。
比如现在有一个专门存储配置的实例:
1. 配置项在仓库中存在,则将此实例的配置文件项的属性值设置成仓库里的值。
2. 配置项在仓库中不存在,则将此实例的配置文件项的属性值设置成默认值。(默认值可能来源于静态文件、也可能直接来源于数据类型(由内存擦除后获得)) 。
也就是说,第二次扫描后,配置信息才会真正的填充到实例中。
3. ReloadablePropertiesFactoryBean
1. 定义哪些Bean是需要被托管的配置类,它会经历配置成仓库容器模块->扫描模块->下载模块->watch模块
2. 记录哪些配置与哪些Bean的属性绑定。
import com.baidu.disconf.client.DisconfMgrBean;
import com.baidu.disconf.client.DisconfMgrBeanSecond;
import com.baidu.disconf.client.addons.properties.ReloadablePropertiesFactoryBean;
import com.google.common.collect.ImmutableList;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
@Configuration(value = "disConfConfiguration")
public class DisConfConfiguration {
/**
* DisconfMgrBean 下载远端disconf中的配置文件、扫描本地静态配置类以及配置文件信息,最终将所有的数据整合并入库
* 所谓的入库就是将配置信息存储到一个Map集合中
* DisconfMgrBean着重用处理文件
* @return DisconfMgrBean
*/
@Bean
public DisconfMgrBean getDisconfMgrBean(){
DisconfMgrBean bean = new DisconfMgrBean();
// 配置类所在的包路径
bean.setScanPackage("uyun.unipass.config.disconf");
return bean;
}
/**
* DisconfMgrBean2 扫描配置项或配置文件的回调函数,获取并处理指定的配置文件实例,接着处理配置文件中的所有文件项
* 比如:
* 1. 配置项在仓库中存在,则将此实例的配置文件项的域值设置成仓库里的值。
* 2. 配置项在仓库中不存在,则将此实例的配置文件项的域值设置成默认值。 (默认值可能来源于静态文件、也可能直接来源于数据类型(由内存擦除后获得))
* DisconfMgrBean2着重用处理文件中的每一条文件项。
* 显然,DisconfMgrBeanSecond一定要在DisconfMgrBean之后执行/生成
*/
@Bean(destroyMethod = "destroy", initMethod = "init")
public DisconfMgrBeanSecond getDisconfMgrBean2(){
return new DisconfMgrBeanSecond();
}
@Bean(name = "reloadablePropertiesFactoryBean")
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public ReloadablePropertiesFactoryBean reloadablePropertiesFactoryBean() {
ReloadablePropertiesFactoryBean propertiesFactoryBean = new ReloadablePropertiesFactoryBean();
List<String> remoteFileNames = ImmutableList.of("classpath*:common.properties", "classpath*:platform-unipass.properties");
propertiesFactoryBean.setSingleton(true);
propertiesFactoryBean.setLocations(remoteFileNames);
return propertiesFactoryBean;
}
}
2.3 项目配置类
以上只是让Disconf托管了配置文件,程序本身并不会自动的重新重载(reload)值到指定的属性上,若想实现此功能,则需要使用以下注解。注意,如果使用了Spring的注解,如@Component,则一定要将项目配置类申明为单例@Scope(singleton),这是因为@Component注解没有使用cglib动态代理,每一次提供的对象都是一个全新的对象,这样一来disconf就不知道往哪个对象中注入属性值了。使用时请务必通过getXXX()方法来获取配置值,这样做才能达到动态获取值得效果。
1. @DisconFile(filename = "xxx.properties")
当前项目配置类对应哪一份配置文件
2. @DisconfFileItem
项目配置类的属性(域)对应配置文件中的哪一条配置信息的key。
import com.baidu.disconf.client.common.annotations.DisconfFile;
import com.baidu.disconf.client.common.annotations.DisconfFileItem;
import com.baidu.disconf.client.common.annotations.DisconfUpdateService;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.context.annotation.Scope;
import uyun.unipass.config.ConfigCentral;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* disconfig common 配置文件
* @author mamr
*/
@Slf4j
@ToString
@Setter
@DisconfFile(filename = "common.properties")
@SuppressWarnings({"unused"})
public class CommonConfig {
/**
* zookeeper服务地址
*/
private String zkUrl;
@DisconfFileItem(name = "zk.url")
public String getZkUrl() {
return zkUrl;
}
}
2.4 disconf.properties
# 是否使用远程配置文件
# true:会从远端获取配置文件 false: 直接使用本地配置文件 默认值:true
enable.remote.conf=true
# Disconf服务器的地址,若有多个,则用英文逗号分隔
conf_server_host=192.168.0.10/disconf
# 版本, 请采用 X_X_X_X 格式
version=1_0_0
# APP 请采用 产品线_服务名 格式
app=prometheus_demo
# 环境
env=local
# debug
debug=false
# 忽略哪些分布式的配置
ignore=
# 获取远程配置 重试次数,默认是3次
conf_server_url_retry_times=10
#获取远程配置 重试时休眠的时间间隔,默认是5秒
conf_server_url_retry_sleep_seconds=3
三. Disconf中关键类的作用
3.1 ReloadConfigurationMonitor
TimeTask定时任务类。每隔一段时间,从远端拉取最新的配置文件,使用File lastModified()获取文件最后一次修改时间,并与本地配置文件作对比,若有差异,则说明远端配置文件被修改,重新将新值填充(刷新)到项目配置类中。
3.2 IDisconfUpdate
当远端(disconf服务器上)的配置更新时,我们可以通过实现这个接口,定义自己的回调函数(实现reload方法)。
注意:
1. 当配置更新后,这个接口会被调用两次!
2. 实现IDisconfUpdate的类一定要受到Spring的管理。
四. 写在最后
本文只是简单的实现了Spring集成Disconf 实现配置动态获取的功能,如果要在其它配置类中读取disconf的动态配置属性,尤其是在项目启动时,另一个Configuration内声明的@Bean对象中需要用到Disconf动态配置值时,则还需要做一些额外的工作。具体请参考我的另一篇博客: 传送门