一、Disconf两个核心bean配置
<bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean" destroy-method="destroy"> <property name="scanPackage" value="com.example.disconf.demo"/> </bean> <bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond" init-method="init" destroy-method="destroy"> </bean>
用过Disconf的小伙伴都知道,这两个Bean配置是必不可少的,那么这两个Bean的作用是什么呢?其实很简单,Disconf有一个Web端,第一个DisconfMgrBean的作用就是从Web端下载配置文件,然后解析配置文件将配置信息存到Disconf配置仓库中,而DisconfMgrBeanSecond的作用就是给带有@DisconfFile注解的bean或者带有@DisconfItem注解的配置项注值。
二、DisconfMgrBean
首先DisconfMgrBean实现了BeanDefinitionRegistryPostProcessor、PriorityOrdered和ApplicationContextAware三个接口,这三个接口都是Spring中的回调接口。PriorityOrdered接口可有可无,实现ApplicationContextAware接口回调获取Spring上下文,重点是这个BeanDefinitionRegistryPostProcessor接口,这个接口继承自BeanFactoryPostProcessor。
熟悉Spring框架的都知道这是Spring中BeanFactory的后处理器,BeanDefinitionRegistryPostProcessor对BeanFactoryPostProcessor进行了扩展,可以直接通过BeanDefinitionRegistry注册bean。那么为什么要实现BeanDefinitionRegistryPostProcessor这个接口呢?原因在于实现这个接口的bean会在其它bean实例化之前优先实例化,同时postProcessBeanDefinitionRegistry方法会在实例化时被Spring回调,正是在这个回调方法里Disconf完成了从web端下载配置文件、监控zk节点等初始化操作。
三、postProcessBeanDefinitionRegistry方法中操作详解
1、扫描地址去重后转换为list,注入applicationContext到DisconfMgr实例。
2、调用DisconfMgr实例的firstScan方法进行第一次扫描,下面看看这个firstScan方法干了些啥。
/** * 第一次扫描,静态扫描 for annotation config */ protected synchronized void firstScan(List<String> scanPackageList) { // 该函数不能调用两次 if (isFirstInit) { LOGGER.info("DisConfMgr has been init, ignore........"); return; } // // // try { // 导入配置 ConfigMgr.init(); LOGGER.info("******************************* DISCONF START FIRST SCAN *******************************"); // registry Registry registry = RegistryFactory.getSpringRegistry(applicationContext); // 扫描器 scanMgr = ScanFactory.getScanMgr(registry); // 第一次扫描并入库 scanMgr.firstScan(scanPackageList); // 获取数据/注入/Watch disconfCoreMgr = DisconfCoreFactory.getDisconfCoreMgr(registry); disconfCoreMgr.process(); // isFirstInit = true; LOGGER.info("******************************* DISCONF END FIRST SCAN *******************************"); } catch (Exception e) { LOGGER.error(e.toString(), e); } }
2.1 调用ConfiMgr的静态方法init进行初始化
/** * 初始化配置 * * @throws Exception */ public synchronized static void init() throws Exception { LOGGER.info("--------------- LOAD CONFIG START ---------------"); // LOGGER.info("Finer print: " + DisClientComConfig.getInstance().getInstanceFingerprint()); // 导入系统配置 DisClientSysConfig.getInstance().loadConfig(null); // 校验 系统配置 DisInnerConfigHelper.verifySysConfig(); // 导入用户配置 DisClientConfig.getInstance().loadConfig(null); // 校验 用户配置 DisInnerConfigHelper.verifyUserConfig(); isInit = true; LOGGER.info("--------------- LOAD CONFIG END ---------------"); }
2.1.2 通过DisClientSysConfig实例加载系统配置,读取类路径下disconf_sys.properties属性文件中的内容并绑定到DisClientSysConfig的实例变量上。disconf_sys.properties属性文件内容如下:
# 仓库 URL disconf.conf_server_store_action=/api/config # zoo URL disconf.conf_server_zoo_action=/api/zoo # 获取远程主机个数的URL disconf.conf_server_master_num_action=/api/getmasterinfo # 下载文件夹, 远程文件下载后会放在这里 disconf.local_download_dir=./disconf/download
DisClientSysConfig类中有如下实例变量定义:
至于如何注入的,请看:
DisconfAutowareConfig就是专门用来读取属性文件内容并通过反射注入到类中带有指定注解的实例变量上,如下:
/** * 自动导入配置数据,能识别 DisconfFileItem 或 DisInnerConfigAnnotation 的标识 * * @Description: auto ware */ private static void autowareConfig(final Object obj, Properties prop) throws Exception { if (null == prop || obj == null) { throw new Exception("cannot autowareConfig null"); } try { Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(DisconfFileItem.class) || field.isAnnotationPresent(DisInnerConfigAnnotation.class)) { if (Modifier.isStatic(field.getModifiers())) { continue; } String name; String value; if (field.isAnnotationPresent(DisconfFileItem.class)) { name = field.getName(); value = prop.getProperty(name, null); } else { // disconf使用的配置 DisInnerConfigAnnotation config = field.getAnnotation(DisInnerConfigAnnotation.class); name = config.name(); String defaultValue = config.defaultValue(); value = prop.getProperty(name, defaultValue); // using disconf as prefix to avoid env confusion if (value.equals(defaultValue) && name != null) { if (name.contains("disconf.")) { String newName = name.substring(name.indexOf('.') + 1); value = prop.getProperty(newName, defaultValue); } } } field.setAccessible(true); if (null != value) { try { ClassUtils.setFieldValeByType(field, obj, value); } catch (Exception e) { LOGGER.error(String.format("invalid config: %s", name), e); } } } } } catch (Exception e) { throw new Exception("error while autowire config file", e); } }
2.1.4 DisClientConfig实例加载用户配置,读取类路径下disconf.properties属性文件内容并绑定到DisClientConfig的实例变量上,这一步和DisClientSysConfig实例加载系统配置一样,下面的配置是不是很熟悉。
/** * 配置文件服务器 HOST */ public static final String CONF_SERVER_HOST_NAME = "disconf.conf_server_host"; @DisInnerConfigAnnotation(name = DisClientConfig.CONF_SERVER_HOST_NAME) public String CONF_SERVER_HOST; private List<String> hostList; /** * app * * @author * @since 1.0.0 */ public static final String APP_NAME = "disconf.app"; @DisInnerConfigAnnotation(name = DisClientConfig.APP_NAME) public String APP; /** * 版本 * * @author * @since 1.0.0 */ public static final String VERSION_NAME = "disconf.version"; @DisInnerConfigAnnotation(name = DisClientConfig.VERSION_NAME, defaultValue = Constants.DEFAULT_VERSION) public String VERSION = Constants.DEFAULT_VERSION; /** * 主或备 * * @author * @since 1.0.0 */ @DisInnerConfigAnnotation(name = "disconf.maintype") public String MAIN_TYPE; /** * 部署环境 * * @author * @since 1.0.0 */ public static final String ENV_NAME = "disconf.env"; @DisInnerConfigAnnotation(name = DisClientConfig.ENV_NAME, defaultValue = Constants.DEFAULT_ENV) public String ENV = Constants.DEFAULT_ENV; /** * 是否从云端下载配置 * * @author * @since 1.0.0 */ private static final String ENABLE_REMOTE_CONF_NAME = "disconf.enable.remote.conf"; @DisInnerConfigAnnotation(name = DisClientConfig.ENABLE_REMOTE_CONF_NAME, defaultValue = "false") public boolean ENABLE_DISCONF = false; /** * 是否开启DEBUG模式: 默认不开启, * 1)true: 用于线下调试,当ZK断开与client连接后(如果设置断点,这个事件很容易就发生),ZK不会去重新建立连接。 * 2)false: 用于线上,当ZK断开与client连接后,ZK会再次去重新建立连接。 * * @author * @since 1.0.0 */ @DisInnerConfigAnnotation(name = "disconf.debug", defaultValue = "false") public boolean DEBUG = false; /** * 忽略哪些分布式配置 * * @author * @since 1.0.0 */ @DisInnerConfigAnnotation(name = "disconf.ignore", defaultValue = "") public String IGNORE_DISCONF_LIST = ""; private Set<String> ignoreDisconfKeySet = new HashSet<String>(); /** * 获取远程配置 重试次数,默认是3次 * * @author * @since 1.0.0 */ @DisInnerConfigAnnotation(name = "disconf.conf_server_url_retry_times", defaultValue = "3") public int CONF_SERVER_URL_RETRY_TIMES = 3; /** * 用户指定的 下载文件夹, 远程文件下载后会放在这里 * * @author * @since 1.0.0 */ @DisInnerConfigAnnotation(name = "disconf.user_define_download_dir", defaultValue = "./disconf/download") public String userDefineDownloadDir = "./disconf/download"; /** * 获取远程配置 重试时休眠时间,默认是5秒 * * @author * @since 1.0.0 */ @DisInnerConfigAnnotation(name = "disconf.conf_server_url_retry_sleep_seconds", defaultValue = "2") public int confServerUrlRetrySleepSeconds = 2; /** * 让下载文件夹放在 classpath目录 下 * * @author * @since 1.0.0 */ @DisInnerConfigAnnotation(name = "disconf.enable_local_download_dir_in_class_path", defaultValue = "true") public boolean enableLocalDownloadDirInClassPath = true;
2.1.5 校验用户配置
至此,系统配置和用户配置分别被加载到了DisClientSysConfig和DisClientConfig中。
2.2 通过RegistryFactory获取SpringRegistry,实质上就是注入了ApplicationContext实例
2.3 通过ScanFactory的静态方法getScanMgr获取扫描器ScanMgr实例,构造实例时静态扫描器列表会添加配置文件、配置项以及非注解托管的配置文件静态扫描器,如下:
2.4 通过ScanMgr的实现ScanMgrImpl进行第一次包扫描
2.4.1 通过ScanStaticStrategy实现ReflectionScanStatic(反射扫描策略)进行包扫描,这里会通过反射获取带有Disconf相关注解的类信息,并通过ScanStaticModel实例存储,看看这里有啥东西。
其实就是一些带有@DisconfFile的类信息和@DisconfFileItem和@DisconfItem注解的方法信息。
2.4.2 遍历静态扫描器列表,扫描数据进入仓库。
我们主要看配置文件静态扫描器如何扫描入库的,如下:
2.4.2.1 首先对ScanStaticModel进行了转换,我们来看看getDisconfFiles方法:
transformScanFile方法是关键,看看是怎么转换的:
/** * 转换配置文件 */ private static DisconfCenterFile transformScanFile(Class<?> disconfFileClass, Set<Method> methods) { DisconfCenterFile disconfCenterFile = new DisconfCenterFile(); // // class disconfCenterFile.setCls(disconfFileClass); DisconfFile disconfFileAnnotation = disconfFileClass.getAnnotation(DisconfFile.class); // // file name disconfCenterFile.setFileName(disconfFileAnnotation.filename()); // config file target dir path disconfCenterFile.setTargetDirPath(disconfFileAnnotation.targetDirPath().trim()); // file type disconfCenterFile.setSupportFileTypeEnum(SupportFileTypeEnum.getByFileName(disconfFileAnnotation.filename())); // // disConfCommonModel DisConfCommonModel disConfCommonModel = makeDisConfCommonModel(disconfFileAnnotation.app(), disconfFileAnnotation.env(), disconfFileAnnotation .version()); disconfCenterFile.setDisConfCommonModel(disConfCommonModel); // Remote URL String url = DisconfWebPathMgr.getRemoteUrlParameter(DisClientSysConfig.getInstance().CONF_SERVER_STORE_ACTION, disConfCommonModel.getApp(), disConfCommonModel.getVersion(), disConfCommonModel.getEnv(), disconfCenterFile.getFileName(), DisConfigTypeEnum.FILE); disconfCenterFile.setRemoteServerUrl(url); // fields Field[] expectedFields = disconfFileClass.getDeclaredFields(); // // KEY & VALUE // Map<String, FileItemValue> keyMaps = new HashMap<String, FileItemValue>(); for (Method method : methods) { // 获取指定的域 Field field = MethodUtils.getFieldFromMethod(method, expectedFields, DisConfigTypeEnum.FILE); if (field == null) { continue; } // DisconfFileItem disconfFileItem = method.getAnnotation(DisconfFileItem.class); String keyName = disconfFileItem.name(); // access field.setAccessible(true); // get setter method Method setterMethod = MethodUtils.getSetterMethodFromField(disconfFileClass, field); // static 则直接获取其值 if (Modifier.isStatic(field.getModifiers())) { try { FileItemValue fileItemValue = new FileItemValue(field.get(null), field, setterMethod); keyMaps.put(keyName, fileItemValue); } catch (Exception e) { LOGGER.error(e.toString()); } } else { // 非static则为Null, 这里我们没有必要获取其Bean的值 FileItemValue fileItemValue = new FileItemValue(null, field, setterMethod); keyMaps.put(keyName, fileItemValue); } } // 设置 disconfCenterFile.setKeyMaps(keyMaps); return disconfCenterFile; }
就是对DisconfCenterFile实例进行一系列set操作,我们来看DisconfCenterFile中几项关键信息,DisconfCenterFile如下
keyMaps是以@DisconfFileItem的name值作键,FileItemValue由三部分组成,分别是@DisconfFileItem对应的值、Field以及对应的set方法,如下:
此外还有一项关键信息,远程下载地址,我们的配置文件就是根据这个地址下载的,如下:
那这个地址到底长啥样呢,我们瞧一瞧:
哦,原来我们在日志里看到的路径就是在这个鬼地方拼接的,注意这个parameterMap是LinkedHashMap,因此最终路径就是
/api/config/app/file?version=version&app=app&env=env&key=fileName
2.4.2.2 将ScanStaicModel转换为List之后,就是入库,回到这里:
我们看看DisconfStoreProcessorFactory运用的简单工厂模式能产出哪些实例:
我们还是看这个配置文件的仓库算子如何将扫描的数据入库的:
终于到了入库这一步,getInstance实际上就是Disconf中仓库实例,充分运用单例模式,看Disconf中仓库长啥样,肯定很简单:
confFileMap用来存储扫描的配置文件信息,confItemMap用来存储扫描的配置项信息,其中confFileMap以文件名作键,DisconfCenterFile作值。