3 disconf在springboot下动态配置各个属性,基于docker环境

时间:2022-08-02 08:54:51

在上一篇中,我们在springboot项目中简单使用了disconf的配置功能,这一篇我们主要来详解一下disconf的配置文件的动态配置。

来看一下disconf.properties文件

# 是否使用远程配置文件
# true(默认)会从远程获取配置 false则直接获取本地配置
enable.remote.conf=true
#
# 配置服务器的 HOST,用逗号分隔  127.0.0.1:8000,127.0.0.1:8000
#
conf_server_host=nginxhost:80
# 版本, 请采用 X_X_X_X 格式
version=1_0_0_0
# APP 请采用 产品线_服务名 格式
app=test_disconf
# 环境
env=abc
# debug
debug=true
# 忽略哪些分布式配置,用逗号分隔
ignore=
# 获取远程配置 重试次数,默认是3次
conf_server_url_retry_times=1
# 获取远程配置 重试时休眠时间,默认是5秒
conf_server_url_retry_sleep_seconds=1
这里面需要配置的难点在于env在docker的动态配置,打成docker后除了conf_server_host需要设置一下nginxhost的docker link,别的都直接写在那无所谓。

而env的动态配置是个麻烦事,我们希望是只打一个包,在所有环境下都适用,无论是rd、local、online都不用再去修改这个disconf.properties,该怎么做呢?

上一篇官方提供了方法(http://disconf.readthedocs.io/zh_CN/latest/tutorial-client/src/Tutorial9.html),就是通过命令行传入

3 disconf在springboot下动态配置各个属性,基于docker环境

这对于直接用java -jar 来启动该springboot项目是OK的,但是构建到docker里就不行了,dockerfile是提前写好的,里面已经写死了启动命令了,后期我们只能去修改docker的启动端口什么的,无法再传入上面的java -Ddisconf.XXX参数了(或者会的话告诉我一下,就不用下面的步骤了)。那么怎么在不同的环境下动态设置disconf.env参数呢,在使用同一个docker镜像的情况下。

下面来看看源码,我们来了解一下disconf的配置生效的过程。

首先是ConfigMgr.java

3 disconf在springboot下动态配置各个属性,基于docker环境

当启动工程后,会进入到该类init方法,这里面是做disconf的配置加载的工作,然后进入DisClientConfig.java

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.baidu.disconf.client.config;

import com.baidu.disconf.client.config.inner.DisInnerConfigAnnotation;
import com.baidu.disconf.client.support.DisconfAutowareConfig;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class DisClientConfig {
    protected static final Logger LOGGER = LoggerFactory.getLogger(DisClientConfig.class);
    protected static final DisClientConfig INSTANCE = new DisClientConfig();
    protected static final String filename = "disconf.properties";
    private static final String DISCONF_CONF_FILE_PATH_ARG = "disconf.conf";
    private boolean isLoaded = false;
    public static final String CONF_SERVER_HOST_NAME = "disconf.conf_server_host";
    @DisInnerConfigAnnotation(
        name = "disconf.conf_server_host"
    )
    public String CONF_SERVER_HOST;
    private List<String> hostList;
    public static final String APP_NAME = "disconf.app";
    @DisInnerConfigAnnotation(
        name = "disconf.app"
    )
    public String APP;
    public static final String VERSION_NAME = "disconf.version";
    @DisInnerConfigAnnotation(
        name = "disconf.version",
        defaultValue = "DEFAULT_VERSION"
    )
    public String VERSION = "DEFAULT_VERSION";
    @DisInnerConfigAnnotation(
        name = "disconf.maintype"
    )
    public String MAIN_TYPE;
    public static final String ENV_NAME = "disconf.env";
    @DisInnerConfigAnnotation(
        name = "disconf.env",
        defaultValue = "DEFAULT_ENV"
    )
    public String ENV = "DEFAULT_ENV";
    private static final String ENABLE_REMOTE_CONF_NAME = "disconf.enable.remote.conf";
    @DisInnerConfigAnnotation(
        name = "disconf.enable.remote.conf",
        defaultValue = "false"
    )
    public boolean ENABLE_DISCONF = false;
    @DisInnerConfigAnnotation(
        name = "disconf.debug",
        defaultValue = "false"
    )
    public boolean DEBUG = false;
    @DisInnerConfigAnnotation(
        name = "disconf.ignore",
        defaultValue = ""
    )
    public String IGNORE_DISCONF_LIST = "";
    private Set<String> ignoreDisconfKeySet = new HashSet();
    @DisInnerConfigAnnotation(
        name = "disconf.conf_server_url_retry_times",
        defaultValue = "3"
    )
    public int CONF_SERVER_URL_RETRY_TIMES = 3;
    @DisInnerConfigAnnotation(
        name = "disconf.user_define_download_dir",
        defaultValue = "./disconf/download"
    )
    public String userDefineDownloadDir = "./disconf/download";
    @DisInnerConfigAnnotation(
        name = "disconf.conf_server_url_retry_sleep_seconds",
        defaultValue = "2"
    )
    public int confServerUrlRetrySleepSeconds = 2;
    @DisInnerConfigAnnotation(
        name = "disconf.enable_local_download_dir_in_class_path",
        defaultValue = "true"
    )
    public boolean enableLocalDownloadDirInClassPath = true;

    public static DisClientConfig getInstance() {
        return INSTANCE;
    }

    private DisClientConfig() {
    }

    public synchronized boolean isLoaded() {
        return this.isLoaded;
    }

    public synchronized void loadConfig(String filePath) throws Exception {
        if (!this.isLoaded) {
            String filePathInternal = "disconf.properties";
            if (filePath != null) {
                filePathInternal = filePath;
            }

            String disconfFilePath = System.getProperty("disconf.conf");
            if (disconfFilePath != null) {
                filePathInternal = disconfFilePath;
            }

            try {
                DisconfAutowareConfig.autowareConfig(INSTANCE, filePathInternal);
            } catch (Exception var5) {
                LOGGER.warn("cannot find " + filePathInternal + ", using sys var or user input.");
            }

            DisconfAutowareConfig.autowareConfigWithSystemEnv(INSTANCE);
            this.isLoaded = true;
        }
    }

    public List<String> getHostList() {
        return this.hostList;
    }

    public void setHostList(List<String> hostList) {
        this.hostList = hostList;
    }

    public Set<String> getIgnoreDisconfKeySet() {
        return this.ignoreDisconfKeySet;
    }

    public void setIgnoreDisconfKeySet(Set<String> ignoreDisconfKeySet) {
        this.ignoreDisconfKeySet = ignoreDisconfKeySet;
    }
}

这里面就是我们在disconf.properties里配置的各个属性,通过自定义注解加载到属性上。

然后进入到DisconfAutowareConfig.java类中,进行对各个属性的赋值。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.baidu.disconf.client.support;

import com.baidu.disconf.client.common.annotations.DisconfFileItem;
import com.baidu.disconf.client.config.inner.DisInnerConfigAnnotation;
import com.baidu.disconf.client.support.utils.ClassUtils;
import com.baidu.disconf.client.support.utils.ConfigLoaderUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class DisconfAutowareConfig {
    protected static final Logger LOGGER = LoggerFactory.getLogger(DisconfAutowareConfig.class);

    private DisconfAutowareConfig() {
    }

    private static Properties getProperties(String propertyFilePath) throws Exception {
        return ConfigLoaderUtils.loadConfig(propertyFilePath);
    }

    public static void autowareConfigWithSystemEnv(Object obj) throws Exception {
        try {
            Field[] fields = obj.getClass().getDeclaredFields();
            Field[] arr$ = fields;
            int len$ = fields.length;

            for(int i$ = 0; i$ < len$; ++i$) {
                Field field = arr$[i$];
                if (field.isAnnotationPresent(DisInnerConfigAnnotation.class) && !Modifier.isStatic(field.getModifiers())) {
                    DisInnerConfigAnnotation config = (DisInnerConfigAnnotation)field.getAnnotation(DisInnerConfigAnnotation.class);
                    String name = config.name();
                    String value = System.getProperty(name);
                    field.setAccessible(true);
                    if (null != value) {
                        try {
                            ClassUtils.setFieldValeByType(field, obj, value);
                        } catch (Exception var10) {
                            LOGGER.error(String.format("invalid config: %s", name), var10);
                        }
                    }
                }
            }

        } catch (Exception var11) {
            throw new Exception("error while autowareConfigWithSystemEnv autowire config file", var11);
        }
    }

    private static void autowareConfig(Object obj, Properties prop) throws Exception {
        if (null != prop && obj != null) {
            try {
                Field[] fields = obj.getClass().getDeclaredFields();
                Field[] arr$ = fields;
                int len$ = fields.length;

                for(int i$ = 0; i$ < len$; ++i$) {
                    Field field = arr$[i$];
                    if ((field.isAnnotationPresent(DisconfFileItem.class) || field.isAnnotationPresent(DisInnerConfigAnnotation.class)) && !Modifier.isStatic(field.getModifiers())) {
                        String name;
                        String value;
                        if (field.isAnnotationPresent(DisconfFileItem.class)) {
                            name = field.getName();
                            value = prop.getProperty(name, (String)null);
                        } else {
                            DisInnerConfigAnnotation config = (DisInnerConfigAnnotation)field.getAnnotation(DisInnerConfigAnnotation.class);
                            name = config.name();
                            String defaultValue = config.defaultValue();
                            value = prop.getProperty(name, defaultValue);
                            if (value.equals(defaultValue) && name != null && name.contains("disconf.")) {
                                String newName = name.substring(name.indexOf(46) + 1);
                                value = prop.getProperty(newName, defaultValue);
                            }
                        }

                        field.setAccessible(true);
                        if (null != value) {
                            try {
                                ClassUtils.setFieldValeByType(field, obj, value);
                            } catch (Exception var12) {
                                LOGGER.error(String.format("invalid config: %s", name), var12);
                            }
                        }
                    }
                }

            } catch (Exception var13) {
                throw new Exception("error while autowire config file", var13);
            }
        } else {
            throw new Exception("cannot autowareConfig null");
        }
    }

    public static void autowareConfig(Object obj, String propertyFilePath) throws Exception {
        Properties prop = getProperties(propertyFilePath);
        if (null != prop && obj != null) {
            autowareConfig(obj, prop);
        } else {
            throw new Exception("cannot autowareConfig " + propertyFilePath);
        }
    }

    private static void autowareStaticConfig(Class<?> cls, Properties prop) throws Exception {
        if (null == prop) {
            throw new Exception("cannot autowareConfig null");
        } else {
            try {
                Field[] fields = cls.getDeclaredFields();
                Field[] arr$ = fields;
                int len$ = fields.length;

                for(int i$ = 0; i$ < len$; ++i$) {
                    Field field = arr$[i$];
                    if (field.isAnnotationPresent(DisconfFileItem.class) && Modifier.isStatic(field.getModifiers())) {
                        field.setAccessible(true);
                        String name = field.getName();
                        Object value = prop.getProperty(name, (String)null);
                        if (value != null) {
                            ClassUtils.setFieldValeByType(field, (Object)null, String.valueOf(value));
                        }
                    }
                }

            } catch (Exception var9) {
                throw new Exception("error while autowire config file", var9);
            }
        }
    }

    public static void autowareStaticConfig(Class<?> cls, String propertyFilePath) throws Exception {
        Properties prop = getProperties(propertyFilePath);
        if (null == prop) {
            throw new Exception("cannot autowareConfig " + propertyFilePath);
        } else {
            autowareStaticConfig(cls, prop);
        }
    }
}
注意关注这两个方法autowareConfig(Object obj, Properties prop),autowareConfigWithSystemEnv(Object obj),前面那个就是获取disconf.properties里的值,把值赋给变量的。而后面那个就是取系统环境变量的
3 disconf在springboot下动态配置各个属性,基于docker环境

执行顺序是这样的,先读取disconf.properties里的所有属性,然后赋值,譬如将配置文件里的disconf.env定义的rd取出来,赋给变量env。然后再去读取系统环境变量,System.getProperty(name),如果也有值,就覆盖从properties里读取的值,这样就是官方说的从java命令行输入参数就能直接动态覆盖配置文件。

根据这个特性我们就能来定制env了,对的,就是使用环境变量。我们只需要在项目启动时加载disconf.env的环境变量,就能动态指定env了。在docker下,环境变量是很容易设置的。

下面看代码:

package com.tianyalei.disconf.config;

import com.baidu.disconf.client.DisconfMgrBean;
import com.baidu.disconf.client.DisconfMgrBeanSecond;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

/**
 * @author wuweifeng wrote on 2017/10/16.
 */
@Configuration
public class DisConfig implements EnvironmentAware {

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

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

    @Override
    public void setEnvironment(Environment environment) {
        String env = environment.getProperty("disconf");
        if(env != null) {
            System.setProperty("disconf.env", env);
        }
    }
}
系统一启动就会调用setEnvironment方法,我们在这里去获取环境变量disconf,然后调用System.setProperty("disconf.env", env);即可将值赋进去,覆盖配置文件里的了。

3 disconf在springboot下动态配置各个属性,基于docker环境

这个就是本机配的环境变量,只做个演示。将来部署到docker里,docker设置环境变量更为简单,我们就可以使用同一个docker镜像,然后在测试环境和生产环境设置不同的环境变量就OK了。