分布式配置中心disconf第二部(基于注解的实现)

开阔下思路,基于我们对spring的理解,如果要完成基于注解实现配置信息的获取,应该做如下3个工作:

  1. 扫描注解,目的是知道有哪些类、哪些属性,需要从分布式配置项、分布式配置文件获取配置,这一步是明确出需求方
  2. 将远程配置项或文件下载到本地,并装载至内存,还需要对远程配置做监听,目的是保证配置数据变更,使用方能够感知,重新去获取
  3. 配置使用,使用一种技术手段在需要查询配置数据的时候,能够把已经装载至本地内存的数据返回

本文从源码进行分析,会涉及一些spring的内部加载机制、反射框架、AOP实现的一些技术点,如下文:

  • 注解扫描
    实现类DisconfMgrBean,完成的是注解扫描,配置应用组件的注入
    这个实现类继承了BeanDefinitionRegistryPostProcessor, PriorityOrdered, ApplicationContextAware,各位技术同学对spring的这三个接口也许并不陌生,下面顺带复习下继承这几个接口的意义
    1、BeanDefinitionRegistryPostProcessor,这个可以实现bean的动态注册,比如传统的定义一个bean的方式,可以使用注解的@Service、@Resources等,这种方式是静态方式的注册、当然使用xml配置一个bean也属于静态的方式,如果实现一种方式根据不同的场景,选择注册不同的bean,可以借助于继承该接口,重写postProcessBeanDefinitionRegistry方法即可
    2、PriorityOrdered,可以定义继承自该接口的bean所对应的优先级,继承该接口也是为了保证DisconfMgrBean能够高优先执行,重写getOrder接口即可,返回值越小,则优先级越高
    3、ApplicationContextAware,spring会扫描所有继承该接口的类,并且执行继承类所重写的setApplicationContext方法,在DisconfMgrBean这里,用户就可以随时使用ApplicationContext托管的各类bean,等做一些需要依赖spring上下文的操作
    注解扫描,指定了扫描的路径scanPackage、以及需要扫描的注解,这里推荐下作者使用的一款reflections的框架,作为类扫描神器:
            <dependency>
                <groupId>org.reflections</groupId>
                <artifactId>reflections</artifactId>
                <version>0.9.9-RC1</version>
            </dependency>

    针对disconf的几种注解,进行扫描的逻辑不进行赘述了,下面是一个大概的代码注解,感兴趣同学可以细看:

        public void firstScan(List<String> packageNameList) throws Exception {
    
            LOGGER.debug("start to scan package: " + packageNameList.toString());
    
            // 获取扫描对象并分析整合(通过注解方式),整合成map:
            //key 含有disconf注解的class,value:该类含有disconfFileItem的方法集合
            //model属性包含:所有包含disConfFile的注解类,所有含disconfFileItem的注解方法,
            //所有含disconfItem的注解方法
            scanModel = scanStaticStrategy.scan(packageNameList);
    
            // 增加非注解的配置
            scanModel.setJustHostFiles(DisconfCenterHostFilesStore.getInstance().getJustHostFiles());
    
            // 放进仓库,通过三类扫描器进行分别装载到对应的仓库(内存)
            for (StaticScannerMgr scannerMgr : staticScannerMgrList) {
    
                // 扫描进入仓库,就是把需求方装入内存
                scannerMgr.scanData2Store(scanModel);
    
                // 忽略哪些KEY
                scannerMgr.exclude(DisClientConfig.getInstance().getIgnoreDisconfKeySet());
            }
        }

    讲完了上面的注解扫描,DisconfMgrBean还有一项功能,就是AOP组件的注册,只有把自定义的查询配置的切面注册至AOP才能达到应用的目的,如下:
     

       public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    
            // 为了做兼容
            DisconfCenterHostFilesStore.getInstance().addJustHostFileSet(fileList);
    
            List<String> scanPackList = StringUtil.parseStringToStringList(scanPackage, SCAN_SPLIT_TOKEN);
            // unique
            Set<String> hs = new HashSet<String>();
            hs.addAll(scanPackList);
            scanPackList.clear();
            scanPackList.addAll(hs);
    
            // 进行扫描
            DisconfMgr.getInstance().setApplicationContext(applicationContext);
            DisconfMgr.getInstance().firstScan(scanPackList);
    
            // register java bean,把AOP的组件动态注册至spring
            registerAspect(registry);
        }
        private void registerAspect(BeanDefinitionRegistry registry) {
    
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(DisconfAspectJ.class);
            beanDefinition.setLazyInit(false);
            beanDefinition.setAbstract(false);
            beanDefinition.setAutowireCandidate(true);
            beanDefinition.setScope("singleton");
    
            registry.registerBeanDefinition("disconfAspectJ", beanDefinition);
        }

     

  • 配置装载
    需求方明确了,接下来就是需要获取远程配置到本地了,这样距离配置应用更近了一步,看如下逻辑:
                ConfigMgr.init();
    
                LOGGER.info("******************************* DISCONF START FIRST SCAN *******************************");
    
                // registry
                Registry registry = RegistryFactory.getSpringRegistry(applicationContext);
    
                // 扫描器
                scanMgr = ScanFactory.getScanMgr(registry);
    
                // 第一次扫描并入库
                scanMgr.firstScan(scanPackageList);
    
                // 获取数据/注入/Watch,远程数据下载,把配置暂存至内存,增加zk对节点的watch
                disconfCoreMgr = DisconfCoreFactory.getDisconfCoreMgr(registry);
                disconfCoreMgr.process();
    

    具体的逻辑比较清晰,1、下载,2、注入至内存,3、增加监听
     

     private void updateOneConfFile(String fileName, DisconfCenterFile disconfCenterFile) throws Exception {
    
            if (disconfCenterFile == null) {
                throw new Exception("cannot find disconfCenterFile " + fileName);
            }
    
            String filePath = fileName;
            Map<String, Object> dataMap = new HashMap<String, Object>();
    
            //
            // 开启disconf才需要远程下载, 否则就本地就好
            //
            if (DisClientConfig.getInstance().ENABLE_DISCONF) {
    
                //
                // 下载配置
                //
                try {
    
                    String url = disconfCenterFile.getRemoteServerUrl();
                    filePath = fetcherMgr.downloadFileFromServer(url, fileName, disconfCenterFile.getFileDir());
    
                } catch (Exception e) {
    
                    //
                    // 下载失败了, 尝试使用本地的配置
                    //
    
                    LOGGER.error(e.toString(), e);
                    LOGGER.warn("using local properties in class path: " + fileName);
    
                    // change file path
                    filePath = fileName;
                }
                LOGGER.debug("download ok.");
            }
    
            try {
                dataMap = FileTypeProcessorUtils.getKvMap(disconfCenterFile.getSupportFileTypeEnum(),
                        disconfCenterFile.getFilePath());
            } catch (Exception e) {
                LOGGER.error("cannot get kv data for " + filePath, e);
            }
    
            //
            // 注入到仓库中
            //
            disconfStoreProcessor.inject2Store(fileName, new DisconfValue(null, dataMap));
            LOGGER.debug("inject ok.");
    
            //
            // 开启disconf才需要进行watch
            //
            if (DisClientConfig.getInstance().ENABLE_DISCONF) {
                //
                // Watch
                //
                DisConfCommonModel disConfCommonModel = disconfStoreProcessor.getCommonModel(fileName);
                if (watchMgr != null) {
                    watchMgr.watchPath(this, disConfCommonModel, fileName, DisConfigTypeEnum.FILE,
                            GsonUtils.toJson(disconfCenterFile.getKV()));
                    LOGGER.debug("watch ok.");
                } else {
                    LOGGER.warn("cannot monitor {} because watch mgr is null", fileName);
                }
            }
        }

     

  • 配置应用
    这里大概介绍下,所有需要从远程获取的数据,都能够保证,有且正确的数据:
    1、DiconfMgr的secondScan方法,第二次扫描会做两件事,第一是扫描需要做配置更新回调的类,写入内存,第二根据本地内存仓库的配置,对实体做配置的注入:
        public void secondScan() throws Exception {
    
            // 开启disconf才需要处理回调
            if (DisClientConfig.getInstance().ENABLE_DISCONF) {
    
                if (scanModel == null) {
                    synchronized(scanModel) {
                        // 下载模块必须先初始化
                        if (scanModel == null) {
                            throw new Exception("You should run first scan before second Scan");
                        }
                    }
                }
    
                // 操作更新回调的实现类,将回调函数实例化并写入仓库
                ScanDynamicStoreAdapter.scanUpdateCallbacks(scanModel, registry);
            }
        }
        //这是实体属性注入的方法
        private void inject2OneConf(String fileName, DisconfCenterFile disconfCenterFile) {
    
            if (disconfCenterFile == null) {
                return;
            }
    
            try {
    
                //
                // 获取实例
                //
    
                Object object;
                try {
    
                    object = disconfCenterFile.getObject();
                    if (object == null) {
                        object = registry.getFirstByType(disconfCenterFile.getCls(), false, true);
                    }
    
                } catch (Exception e) {
    
                    LOGGER.error(e.toString());
                    object = null;
                }
    
                // 注入实体中
                disconfStoreProcessor.inject2Instance(object, fileName);
    
            } catch (Exception e) {
                LOGGER.warn(e.toString(), e);
            }
        }
    //详细的注入逻辑
    public void inject2Instance(Object object, String fileName) {
    
            DisconfCenterFile disconfCenterFile = getInstance().getConfFileMap().get(fileName);
    
            // 校验是否存在
            if (disconfCenterFile == null) {
                LOGGER.error("cannot find " + fileName + " in store....");
                return;
            }
    
            //
            // 静态类
            //
            if (object != null) {
                // 设置object
                disconfCenterFile.setObject(object);
            }
    
            // 根据类型设置值
            //
            // 注入实体
            //
            Map<String, FileItemValue> keMap = disconfCenterFile.getKeyMaps();
            for (String fileItem : keMap.keySet()) {
    
                // 根据类型设置值
                try {
    
                    //
                    // 静态类
                    //
                    if (object == null) {
    
                        if (keMap.get(fileItem).isStatic()) {
                            LOGGER.debug(fileItem + " is a static field. ");
                            keMap.get(fileItem).setValue4StaticFileItem(keMap.get(fileItem).getValue());
                        }
    
                        //
                        // 非静态类
                        //
                    } else {
    
                        LOGGER.debug(fileItem + " is a non-static field. ");
    
                        if (keMap.get(fileItem).getValue() == null) {
    
                            // 如果仓库值为空,则实例 直接使用默认值
                            Object defaultValue = keMap.get(fileItem).getFieldDefaultValue(object);
                            keMap.get(fileItem).setValue(defaultValue);
    
                        } else {
    
                            // 如果仓库里的值为非空,则实例使用仓库里的值
                            keMap.get(fileItem).setValue4FileItem(object, keMap.get(fileItem).getValue());
                        }
                    }
    
                } catch (Exception e) {
                    LOGGER.error("inject2Instance fileName " + fileName + " " + e.toString(), e);
                }
            }
        }

    2、AOP保证每次请求都会从配置仓库获取最新的配置
    涉及到两类注解一类是含有disconfFileItem注解的所有方法,一类是含有disconfItem注解的所有方法,实现的类是DisconfAspectJ,如下:
    @Aspect
    public class DisconfAspectJ {
    
        protected static final Logger LOGGER = LoggerFactory.getLogger(DisconfAspectJ.class);
    
        @Pointcut(value = "execution(public * *(..))")
        public void anyPublicMethod() {
        }
    
        /**
         * 获取配置文件数据, 只有开启disconf远程才会进行切面
         *
         * @throws Throwable
         */
        @Around("anyPublicMethod() && @annotation(disconfFileItem)")
        public Object decideAccess(ProceedingJoinPoint pjp, DisconfFileItem disconfFileItem) throws Throwable {
    
            if (DisClientConfig.getInstance().ENABLE_DISCONF) {
    
                MethodSignature ms = (MethodSignature) pjp.getSignature();
                Method method = ms.getMethod();
    
                //
                // 文件名
                //
                Class<?> cls = method.getDeclaringClass();
                DisconfFile disconfFile = cls.getAnnotation(DisconfFile.class);
    
                //
                // Field名
                //
                Field field = MethodUtils.getFieldFromMethod(method, cls.getDeclaredFields(), DisConfigTypeEnum.FILE);
                if (field != null) {
    
                    //
                    // 请求仓库配置数据
                    //
                    DisconfStoreProcessor disconfStoreProcessor =
                            DisconfStoreProcessorFactory.getDisconfStoreFileProcessor();
                    Object ret = disconfStoreProcessor.getConfig(disconfFile.filename(), disconfFileItem.name());
                    if (ret != null) {
                        LOGGER.debug("using disconf store value: " + disconfFile.filename() + " ("
                                + disconfFileItem.name() +
                                " , " + ret + ")");
                        return ret;
                    }
                }
            }
    
            Object rtnOb;
    
            try {
                // 返回原值
                rtnOb = pjp.proceed();
            } catch (Throwable t) {
                LOGGER.info(t.getMessage());
                throw t;
            }
    
            return rtnOb;
        }
    
        /**
         * 获取配置项数据, 只有开启disconf远程才会进行切面
         *
         * @throws Throwable
         */
        @Around("anyPublicMethod() && @annotation(disconfItem)")
        public Object decideAccess(ProceedingJoinPoint pjp, DisconfItem disconfItem) throws Throwable {
    
            if (DisClientConfig.getInstance().ENABLE_DISCONF) {
                //
                // 请求仓库配置数据
                //
                DisconfStoreProcessor disconfStoreProcessor = DisconfStoreProcessorFactory.getDisconfStoreItemProcessor();
                Object ret = disconfStoreProcessor.getConfig(null, disconfItem.key());
                if (ret != null) {
                    LOGGER.debug("using disconf store value: (" + disconfItem.key() + " , " + ret + ")");
                    return ret;
                }
            }
    
            Object rtnOb;
    
            try {
                // 返回原值
                rtnOb = pjp.proceed();
            } catch (Throwable t) {
                LOGGER.info(t.getMessage());
                throw t;
            }
    
            return rtnOb;
        }
    }

     

  • 配置更新
    实现逻辑在NodeWatcher类中,前面已经介绍过在第一次扫描时已经创建了监听,触发监听以后,会重新装载内存数据+调用更新回调类,监听事件,当event.getType() == EventType.NodeDataChanged会调用callback,应该是根据文件做的监听,见下文:
        private void callback() {
    
            try {
    
                // 调用回调函数, 回调函数里会重新进行监控
                try {
                    disconfSysUpdateCallback.reload(disconfCoreMgr, disConfigTypeEnum, keyName);
                } catch (Exception e) {
                    LOGGER.error(e.toString(), e);
                }
    
            } catch (Exception e) {
    
                LOGGER.error("monitor node exception. " + monitorPath, e);
            }
        }
    //具体的更新配置和回调执行
        @Override
        public void updateOneConfAndCallback(String key) throws Exception {
    
            // 更新 配置
            updateOneConf(key);
    
            // 回调
            DisconfCoreProcessUtils.callOneConf(disconfStoreProcessor, key);
            callUpdatePipeline(key);
        }

     

转载于:https://my.oschina.net/13426421702/blog/3048558

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值