在上一篇中,我们在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),就是通过命令行传入
这对于直接用java -jar 来启动该springboot项目是OK的,但是构建到docker里就不行了,dockerfile是提前写好的,里面已经写死了启动命令了,后期我们只能去修改docker的启动端口什么的,无法再传入上面的java -Ddisconf.XXX参数了(或者会的话告诉我一下,就不用下面的步骤了)。那么怎么在不同的环境下动态设置disconf.env参数呢,在使用同一个docker镜像的情况下。
下面来看看源码,我们来了解一下disconf的配置生效的过程。
首先是ConfigMgr.java
当启动工程后,会进入到该类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里的值,把值赋给变量的。而后面那个就是取系统环境变量的
执行顺序是这样的,先读取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);即可将值赋进去,覆盖配置文件里的了。
这个就是本机配的环境变量,只做个演示。将来部署到docker里,docker设置环境变量更为简单,我们就可以使用同一个docker镜像,然后在测试环境和生产环境设置不同的环境变量就OK了。