Disconf-Client端原理分析和使用思考

目录

一、结构组成

1.模块组成

2.UML类图

3.数据结构

二、原理流程

1.配置类

2.配置文件

3.注解类

4.原理分析

4.1 Disconf核心加载流程原理

4.2 监听下载文件流程原理

三、使用思考

1.搭配注解的简易使用配置

2.注解方式使用二次加载

3.无侵入式另类的简易配置


本次只基于@DisconfFile和@DisconfFileItem两个注解搭配使用的情况,@DisconfItem等注解的组成和数据结构将不会分析。

一、结构组成

1.模块组成

Disconf的大致组成如下图:

总的来说可以把Disconf分成三个大模块:

  1. 管理模块:这部分模块是与外界进行交互和Disconf启动的入口,其中大部分实现都是通过工厂模式和单例模式完成的,因此如果去看Disconf的源码就会发现,单例模式和工厂模式无处不在,简直泛滥成灾;
  2. 数据模块:这部分包括了Disconf系统默认的配置数据、用户自定义的配置数据、读取Disconf注解类的配置数据和保存已经解析过的数据仓库等;
  3. 执行模块:Disconf官方将其称为算子,实际上是差不多意思,这部分的功能便是使用数据模块的数据和Zookeeper实现具体的某种功能。如数据的拉取,Zookeeper节点的监听等。

2.UML类图

UML组成类图如下:

这个UML类图看起来很臃肿是因为Disconf在执行和分析数据这两个模块中大量的使用了工厂模式,导致本应该一个接口就能解决的扩展到了两个类,因此实际上而言重要的类并不是很多。接下来具体看下Disconf的模块包含了哪些类:

  • 管理模块:UML类图中可以清晰的看到最下面的三个以DisconfMgr开头和DisconfCoreMgr接口,也就是图中的正下方和左下角的几个类和接口;
  • 分析数据模块:UML类图中左上角ScanMgr接口和对应的工厂实现类,这部分的作用便是扫描包路径并且解析扫描到的类是否含有Disconf的注解,最后将解析过后的数据存放到存储数据模块的仓库中;
  • 存储数据模块:在UML类图中由中间正上方以DisconfCenter开头为核心的类组成,存储了系统、用户和解析扫描包注解的数据;
  • 拉取模块:以FetchMgr接口和其对应的工厂实现类组成,再搭配系列的Processor完成模块功能;
  • 监听模块:以WatchMgr接口和其对应的工厂实现类组成,再搭配系列的Processor完成模块功能。

3.数据结构

相关数据结构图如下:

值得一提的是DisconfCenterStore这个Disconf的仓库类里面中的很多成员属性,在此我们只分析confFileMap对象。这个对象的作用便是把@DisconfFile注解中的fileName属性当成key,再将这个文件对应的各种属性使用DisconfCenterFile类封装保存,一一对应形成k-v键值对。

而在DisconfCenterFile类中,封装了注入实例对象,对象的class类型和keyMaps集合对象,keyMaps集合对象中存储的便是@DisconfFileItem注解的name配置名和对应的FileItemValue对象键值对,而在FileItemValue对象中又有@DisconfFileItem注解的对应字段和字段对应的setter方法。

二、原理流程

1.配置类

我们从Disconf的官方文档可以看到如果使用配置类,其推荐的配置方法如下:

@Configuration
public class DisconfConfiguration {

    @Bean(destroyMethod = "destroy")
    public DisconfMgrBean disconfMgrBean() {
        DisconfMgrBean disconfMgrBean = new DisconfMgrBean();
        disconfMgrBean.setScanPackage("com.XXXX.disconf");
        return disconfMgrBean;
    }

    @Bean(initMethod = "init", destroyMethod = "destroy")
    public DisconfMgrBeanSecond disconfMgrBeanSecond() {
        return new DisconfMgrBeanSecond();
    }

    @Bean(name = "reloadable_disconf")
    public ReloadablePropertiesFactoryBean reloadable() {
        ReloadablePropertiesFactoryBean bean = 
                new ReloadablePropertiesFactoryBean();
        bean.setLocations(
                Collections.singletonList(
                        Constants.CLASSPATH_FILE
                )
        );

        return bean;
    }

    @Bean(name = "propertyConfigurer")
    public ReloadingPropertyPlaceholderConfigurer properties(
            @Qualifier("reloadable_disconf") 
            ReloadablePropertiesFactoryBean 
                    reloadablePropertiesFactoryBean) throws Exception {
        ReloadingPropertyPlaceholderConfigurer ppc = 
                new ReloadingPropertyPlaceholderConfigurer();
        ppc.setPropertiesArray(new Properties[]{
                reloadablePropertiesFactoryBean.getObject()});
        ppc.setIgnoreUnresolvablePlaceholders(true);
        ppc.setIgnoreResourceNotFound(true);
        return ppc;
    }

}

接下来分别大致说一下各个对象的作用:

  1. DisconfMgrBean:这个类的大致作用有三点:
    1. 扫描加载静态的disconf注解类,包括@DisconfFile和@DisconfFileItem等这些注解属性;
    2. 获取数据,将数据注入到Disconf仓库中,在获取完数据后监听Zookeeper节点,当节点的数据发生变动时将同时Client端更新数据;
    3. 注册切面对象DisconfAspectJ,用来增强被@DisconfFile和@DisconfFileItem等注解过的类和方法。
  2. DisconfMgrBeanSecond:其作用有二:
    1. 获取DisconfMgrBean扫描到@DisconfUpdateService注解配置的回调类,并将其添加到对应的文件属性对象的disconfCommonCallbackModel中;
    2. 调用FileItemValue对象中的setMethod方法对象,并通过反射将对应的值设置到对象中。
  3. ReloadablePropertiesFactoryBean:对本机器文件系统中的某些文件进行监听,如果被监听的文件被改动过则修改对应属性的值,并发布文件被修改事件;
  4. ReloadingPropertyPlaceholderConfigurer:与ReloadablePropertiesFactoryBean搭配使用的刷新对象,当被监听的文件发生改变后则调用监听器的propertiesReloaded方法。该方法将会调用对应的属性对象setter方法进行赋值。

2.配置文件

配置好了上面四个关键bean,再配置一下disconf的用户配置文件:

# 是否使用远程配置文件,true会从远程获取配置,false会直接获取本地配置
disconf.enable.remote.conf=true
# disconf服务器,多个使用逗号分割
disconf.conf_server_host=XXX.XX.XX.XXX:XXXX
# disconf使用环境,优先使用注解的,注解没配置则使用配置文件的
disconf.env=DEV
# app版本号,优先使用注解的,注解没配置则使用配置文件的
disconf.version=v1.0.0
# app名称,优先使用注解的,注解没配置则使用配置文件的
disconf.app=XXXXXXXX
# 忽略哪些分布式配置文件,多个使用逗号分割文件名,如果配置了则不会从远程下载
disconf.ignore=
# 获取远程配置超时重试次数,默认3次
disconf.conf_server_url_retry_times=2
# 获取远程配置超时重试时休眠时间,单位秒
disconf.conf_server_url_retry_sleep_seconds=1
# 自定义远程配置文件下载路径,根路径为系统下
disconf.user_define_download_dir=disconf/remoteFile
# 是否下载到classpath目录下,默认true,推荐为true
# 因为如果配置的false则有可能导致临时文件无法删除
disconf.enable_local_download_dir_in_class_path=true

配置项以及对应相关的功能已经写在了配置中。

3.注解类

最后再配置注解类即可:

@Component
@DisconfFile(filename = Constants.DISCONF_FILE)
public class VirtualConfig {

    private String defaultMessage;

    @DisconfFileItem(name = "defaultMessage")
    public String getDefaultMessage() {
        return defaultMessage;
    }

    public void setDefaultMessage(String defaultMessage) {
        this.defaultMessage = defaultMessage;
    }
}

4.原理分析

我们知道Disconf一共对外的配置类官方的Demo只有四个(也不算少),其实那四个配置类可以分为两个原理实现:一个是实现Disconf配置和注解加载,并且拉取和监听ZK节点的功能;另一个是对已经下载下来的文件进行监听和回调bean赋值功能。接下来分析大致说明一下。

4.1 Disconf核心加载流程原理

原理流程图如下:

Disconf的核心加载流程我们可以看成就是两次加载,第一次加载为DisconfMgrBean,而第二次则是DisconfMgrBeanSecond,其各自实现了什么功能便不做过多介绍,前面已经分析过了,接下来大致说一下第一次加载和第二次加载的大致作用。

第一次加载:这一次加载是最重要的,这一次会初始化Disconf依赖的执行模块、数据模块和管理模块,并调用执行模块去分析和存储数据,以获得前面分析过的数据结构。在获得所需的数据结构后再调用和监听ZK用来完成拉取和更新文件的操作。当然注册DisconfAspectJ也是这部分功能的重中之重,因为后续调用被@DisconfFile和@DisconfFileItem注解过的类和方法时将直接使用这个增强切面从Disconf仓库中获取。

第二次加载:这一次加载可以说是锦上添花,添加注解回调类,并且调用第一次加载获得对应对象的setter方法为Disconf托管的类进行赋值,但是实际上因为有DisconfAspectJ的存在,因此调用被@DisconfFileItem注解的getter方法时,将直接从Disconf仓库中获取值,而不是获取的实际成员属性,因此这里的setter作用仅仅是让托管的配置类属性像是更新了。

4.2 监听下载文件流程原理

原理流程图如下:

这个监听流程功能可以看成是Disconf功能的扩展,较之于核心加载流程而言算是可有可无,因为这部分的作用对象是已经下载下来的配置文件,如果配置文件下载下来,则说明Disconf仓库中的数据也一定是最新的,要想获取最新的配置直接从Disconf仓库中获取即可,而无需使用这一套监听文件,监听到文件发生改变再调用bean的setter方法进行赋值。

三、使用思考

1.搭配注解的简易使用配置

如果要实现的仅仅只是Disconf-Web端实时的获取最新配置信息,那么完全可以不使用监听文件那一套,甚至Disconf的第二次加载流程也可以忽略。

在配置类中配置DisconfMgrBean类,配置如下:

@Configuration
public class DisconfConfiguration {
    @Bean(destroyMethod = "destroy")
    public DisconfMgrBean disconfMgrBean() {
        DisconfMgrBean disconfMgrBean = new DisconfMgrBean();
        disconfMgrBean.setScanPackage("com.iboxpay.disconf");
        return disconfMgrBean;
    }
}

disconf.properties文件照旧,配置类如下:

@Component
@DisconfFile(filename = Constants.DISCONF_FILE)
public class VirtualConfig {

    private String defaultMessage;

    @DisconfFileItem(name = "defaultMessage")
    public String getDefaultMessage() {
        return defaultMessage;
    }
}

@DisconfFileItem的associateField属性如果没有配置会自动取get后面头一个字符小写的名字,因此正常情况下可以忽略这个属性,当需要使用defaultMessage属性时直接调用getter方法即可完成动态配置获取,因为DisconfAspectJ切面会直接从Disconf仓库中获取,字段defaultMessage实际有没有值无需关心,只要getter返回正确的值即可。

2.注解方式使用二次加载

既然使用Disconf的第一次加载就可以完成动态获取Disconf配置项的作用,那么第二次加载到底给我们用来干嘛的?实际上当我们需要自定义刷新回调逻辑,如自己实现和Spring的Environment对象对接的逻辑便可以实现IDisconfUpdate接口再使用@DisconfUpdateService注解来完成;再比如我们需要使用到配置类的具体字段属性,在配置类中使用这些字段数形再进行一些逻辑处理等(当然,这种实现方式是有问题的,如果需要对某些配置项进行额外特定的处理,使用设计模式的委派模式来创建一个缓存委派对象是最佳的)。

3.无侵入式另类的简易配置

使用@DisconfFile和@DisconfFileItem等注解来获取配置对代码的侵入性太大,要一直使用注解维护来维护配置类,比较麻烦。接下来要介绍的便是无代码侵入,直接调用方法即可完成获取Disconf配置项的功能。

Disconf经过了第一次加载后,所有的数据以及以后更新后的数据都会存储在Disconf仓库中,且这个仓库实例是一个静态单例,所以我们可以直接从这个仓库中获取数据。由此我们便可以使用另一种种简易的配置方法。

配置类如下:

@Configuration
public class DisconfConfiguration {
    @Bean(name = "reloadable_disconf")
    public ReloadablePropertiesFactoryBean reloadable() {
        DisconfMgr.getInstance().start(new ArrayList<>());
        ReloadablePropertiesFactoryBean bean = 
                new ReloadablePropertiesFactoryBean();
        bean.setLocations(
                Collections.singletonList(
                        Constants.DISCONF_FILE
                )
        );
        return bean;
    }
}

DisconfMgr实例的start方法里面会调用第一次加载和第二次加载,传个空的List进去将不会进行数据注入拉取等操作,只会进行Disconf核心模块的初始化。接着再使用setLocations方法,这个方法将会去进行数据拉取监听节点等动作。由此便使用了一个配置完成了Disconf数据的获取和监听。这样虽然会进行第二次加载浪费一些性能,但是由于没有什么数据需要操作,因此浪费的时间性能可以忽略不计。

接着定义一个直接从仓库获取数据的类:

public class DisconfVirtualHolder {

    /**
     * 从virtual文件中获取值
     *
     * @param key
     * @return
     */
    public static String getVirtualConfig(String key) {
        return getString(DisconfDataGetter
                .getByFileItem(Constants.DISCONF_FILE, key));
    }

    /**
     * 转变成String对象
     *
     * @param obj
     * @return
     */
    private static String getString(Object obj) {
        if (obj != null) {
            if (obj instanceof String) {
                return (String) obj;
            } else if (obj instanceof Integer) {
                return ((Integer) obj).toString();
            } else if (obj instanceof Long) {
                return ((Long) obj).toString();
            }
        }
        return null;
    }

}

DisconfDataGetter是Disconf官方给的从Disconf仓库中根据文件名和对应的配置项获取数据,写的例子这里不过是直接指定了文件,只传配置项而已。当我们要使用Disconf的配置时,直接调用getVirtualConfig方法即可,如果有其它的文件,增加一个方法固定文件名即可。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值