Apollo(4) Client包

作者: Oliver_Li | 来源:发表于2022-12-09 22:41 被阅读0次
    一、常用使用方式和功能:
    1. apollo引入方式:
    • Java配置:@EnableApolloConfig
    • spring boot:apollo.bootstrap.enabled = true
    1. 常见用法:
    • @Value自动更新
    • @ConfigurationProperties + @RefreshScop
    • @ApolloConfig注入Config对象手动获取
    • 配置文件占位符自动替换如,name = ${xx}
    • 配置完全托管给apollo
    二、Spring周期和Apollo相关扩展点:
    • EnvironmentPostProcessor:Spring收集配置(系统变量、环境变量等)的后置扩展,用于处理Environment,使用里面的PropertySource进行配置存取,这时还没有开始bean相关的操作,Apollo在这个阶段提前把从远程获取到的配置放进PropertySource,完成第一步的配置初始化
    • ImportBeanDefinitionRegistrar:Spring解析并生成BeanDefinition对象阶段,Apollo在这个阶段加入了动态更新等处理器,Client主要看的也是这部分
    • BeanFactoryPostProcessor:bean工厂后置扩展
    • Bean实例化,调用构造函数等
    • BeanPostProcessor:Bean初始化前处理器
    • Bean初始化
    • BeanPostProcessor:Bean初始化后处理器
    • 销毁(略)
    三、代码部分:
    1. 官方对Client流程的整体描述,接Config Service长轮询部分


    2. Config、ConfigRepository:Apollo对配置和仓库等概念的抽象
    • ConfigRepository:抽象了配置仓库。包括远程仓库(从Config Service拿到的数据)、本地仓库(本地文件缓存)


      image
    • LocalFileConfigRepository:代表本地文件配置库,获取远程仓库配置时使用远程配置,更新时同时同步本地缓存文件,本地配置算远程配置的降级方案,在源码中远程仓库是本地仓库的“上游”
    • RemoteConfigRepository:代表远程仓库。配置查询上两种机制并行,一是定时任务5分钟一次从config service查询配置,二是长轮询,如下
    • RemoteConfigLongPollService:实现长轮询请求的类,之前看到了Config Service里的服务端,Config Service有更新就会返回200,但更新数据不会返回配置,只能知道哪个namespace,根据namespace再去查询更新,RemoteConfigRepository则继续长轮询。如果超时返回304后也是继续轮询,客户端超时90s,服务端超时60s。
    • Config:代表具体配置,这块很杂有兴趣可以自行查看代码
    1. 启动方式apollo.bootstrap.enabled和@EnableApolloConfig:
    • apollo.bootstrap.enabled是基于SpringBoot配置文件的
    • @EnableApolloConfig是基于JavaConfig的,这种包含了SpringBoot方式所有逻辑还附加了逻辑,所以以这种为主轴看代码,和上一种的差异原因也会写出来
    1. @EnableApolloConfig注解:
    • 注解里@Import(ApolloConfigRegistrar.class),ApolloConfigRegistrar类实现了ImportBeanDefinitionRegistrar,执行在创建Bean之前收集BeanDefinition阶段,是很靠前的一个扩展点,Apollo加入了几个自定义Bean,下面按执行Bean主逻辑执行时间线进行分析
    1. 准备阶段ApolloApplicationContextInitializer:
    • 通过spring.factories扫描加载,实现了两个接口ApplicationContextInitializer和EnvironmentPostProcessor所以有两个阶段逻辑
    • EnvironmentPostProcessor#postProcessEnvironment():①
      • 这里有个配置apollo.bootstrap.eagerLoad.enabled,可以提前在这个阶段(日志系统加载前)执行ApplicationContextInitializer#initialize()逻辑,加载Apollo远程配置
    • ApplicationContextInitializer#initialize():②
      • 这里初始化了environment的ApolloBootstrapPropertySources,里面的配置从ConfigRepository(Config Service)拿到的,这时Bootstrap数据源就有数据了,后面在PropertySourcesProcessor中会用到
    1. DefaultApolloConfigRegistrarHelper加入Apollo自定义Bean:③
      @Override
      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 获取所有@EnableApolloConfig注解和参数
        AnnotationAttributes attributes = AnnotationAttributes
            .fromMap(importingClassMetadata.getAnnotationAttributes(EnableApolloConfig.class.getName()));
        final String[] namespaces = attributes.getStringArray("value");
        final int order = attributes.getNumber("order");
        // 解析namespaces里的占位符并添加到PropertySourcesProcessor
        final String[] resolvedNamespaces = this.resolveNamespaces(namespaces);
        PropertySourcesProcessor.addNamespaces(Lists.newArrayList(resolvedNamespaces), order);
    
        // 配置PropertySourcesPlaceholderConfigurer优先级
        Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
        propertySourcesPlaceholderPropertyValues.put("order", 0);
    
        // 处理配置里的占位符(BeanFactoryPostProcessor)
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
        // 更新@Value实现不用@RefreshScope刷新
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, AutoUpdateConfigChangeListener.class);
        // 处理@EnableApolloConfig的namespace,(配置文件启动方式里没有这个注解,所以去掉了这部分)
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class);
        // 处理@ApolloConfig、@ApolloJsonValue、@ApolloConfigChangeListener并注入相关信息
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class);
        // 收集@Value和xml的配置信息放入SpringValueRegistry
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class);
        // 把xml中的配置信息提取出来key 占位符等等并存储,在SpringValueProcessor作出处理
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class);
      }
    
    1. SpringValueDefinitionProcessor:④
    • 实现BeanDefinitionRegistryPostProcessor,也是处理BeanDefinition阶段,用于处理xml配置占位符
    • 用双层map保存了BeanDefinitionRegistry、beanName、SpringValueDefinition的映射关系,SpringValueDefinition里存了xml有哪些占位符和相关替换的信息,后续SpringValueProcessor会用到
    1. PropertySourcesProcessor:⑤
    • 实现了BeanFactoryPostProcessor,在之前registerBeanDefinitions()中有个和本类相关的逻辑,把@EnableApolloConfig的namespaces和order属性加入到了本类,所以大概能知道这个类是处理注解下namespaces的,接ApolloApplicationContextInitializer部分
    • Client启动时用户指定了app、env、cluster能定位项目的具体配置,ApolloBootstrapPropertySources里存了定位下的每个namespace,每个namespace都被算做一个配置数据源,数据源下面的source属性代表对应的一组配置项,除此之外还有一个特殊的源ApolloPropertySources代表@EnableApolloConfig标记的数据源的就是在这处理的
    • 主要两个逻辑
      • 初始化数据源:这里把ApolloBootstrapPropertySources的优先级放到了第一,第二位就是ApolloPropertySources,这样优先使用ApolloBootstrapPropertySources
      • 添加监听:数据源里的每个configPropertySource都添加了修改配置时的回调,回调触发ApolloConfigChangeEvent,AutoUpdateConfigChangeListener逻辑最后说
    1. PropertySourcesPlaceholderConfigurer:⑥
    • 本身是BeanFactoryPostProcessor,注册后会在BeanFactory后置阶段执行,用于对XML和@Value里的“${xx}”占位符进行解析,配置来源于environment
    1. SpringValueProcessor:
    • 实现了BeanPostProcessor和BeanFactoryPostProcessor,所以有Bean初始化before和BeanFactory阶段两个扩展逻辑,主要用于@Value和xml配置的收集并注册到SpringValueRegistry
    • BeanFactoryPostProcessor#postProcessBeanFactory:⑦
      • 把之前SpringValueDefinitionProcessor里获取到的xml SpringValue取出备用
    • BeanPostProcessor#postProcessBeforeInitialization:⑨
      • 首先收集@Value相关信息组装成SpringValue注册到SpringValueRegistry,然后处理xml的SpringValue,同样流程
    1. ApolloAnnotationProcessor:⑧
    • 是一个BeanPostProcessor,这里主要在before阶段处理四个注解@ApolloConfig、@ApolloJsonValue、@ApolloConfigChangeListener
    • @ApolloConfig:用在字段上,可根据namespace注入Config对象,config对象携带了配置信息
    • @ApolloConfigChangeListener:用在方法上,可根据namespace监听配置变更事件并传入ConfigChangeEvent携带了配置变更信息
    • @ApolloJsonValue:用在字段和方法上,把JSON字符串转成对象注入
    • 知道了注解含义后处理就比较简单了,@ApolloConfig直接从ConfigService获取了Config,赋值给字段,Config根本上都是从上面提到Configrepository获取,@ApolloConfigChangeListener逻辑是给Config添加了一个监听回调,回调时用invokeMethod把配置相关信息写进去,@ApolloJsonValue类似有兴趣可以看看
    1. AutoUpdateConfigChangeListener:配置更新时@Value实现自动更新的Bean!⑩
    • 一共两个监听,ConfigChangeListener、事件ApolloConfigChangeEvent,最后都是调用onChange(),onChange()负责触发配置修改时把@Value的字段set成新值,完成配置自动更新
    • onChange()第一步先获取到所有需要修改的key
    • 从之前收集xml和@Value的SpringValueRegistry里查看是否存在,之前提到SpringValueRegistry缓存了所有@Value、xml标注的字段、方法,不存在说明不需要Apollo控制更新跳过
    • PS:SpringValue代表某个@Value的所有信息。包括Key、占位符格式如"${ccc:0}、所属Bean、对应的字段Field等
    • 更新流程:利用PlaceholderHelper从environment解析出占位符的value,并通过SpringValue里的反射方法更新对应字段value,到这里@Value就是新值了,所以总结来说还是用的反射完成的自动更新
    public class PlaceholderHelper {
        ...
        /**
       * 把占位符解析成具体值
       */
        public Object resolvePropertyValue(ConfigurableBeanFactory beanFactory, String beanName, String placeholder) {
        // 获取解析的值
        String strVal = beanFactory.resolveEmbeddedValue(placeholder);
    
        BeanDefinition bd = (beanFactory.containsBean(beanName) ? beanFactory
            .getMergedBeanDefinition(beanName) : null);
    
        // resolve expressions like "#{systemProperties.myProp}"
        return evaluateBeanDefinitionString(beanFactory, strVal, bd);
      }
        ...
    }
    
    public class SpringValue {
      ......
      // field.set()给字段设置新值
      private void injectField(Object newVal) throws IllegalAccessException {
        Object bean = beanRef.get();
        if (bean == null) {
          return;
        }
        boolean accessible = field.isAccessible();
        field.setAccessible(true);
        field.set(bean, newVal);
        field.setAccessible(accessible);
      }
    
      // 用方法设置新值
      private void injectMethod(Object newVal)
          throws InvocationTargetException, IllegalAccessException {
        Object bean = beanRef.get();
        if (bean == null) {
          return;
        }
        methodParameter.getMethod().invoke(bean, newVal);
      }
        ......
    }
    
    1. 总结:Clinet部分主要了解一是对配置,包括远程、本地的抽象。二是Apollo在Spring的关键扩展点做的处理;最后就是长轮训Client怎么和meta service、config service通信

    源码里相关的知识点:

    1. Environment和PropertySource:https://www.codenong.com/jsf6f51ff3e0f2/

    2. 总结:Environment包括Java环境变量、系统环境变量等。PropertySource代表数据源,以kv形式使用配置,Environment里加入了各种源的PropertySource直接使用,也可以自定义配置数据源到Environment。apollo里所有namespace都放在了Environment下的PropertySource,PropertySource下的source获取具体配置kv

    3. @Value:https://blog.csdn.net/weixin_46228112/article/details/124623568

    4. 总结:@Value数据来源于PropertySource或者间接说是Environment,但改变PropertySource并不会改变运行时的值,有两种方式一是@RefreshScope刷新bean,二是apollo里用反射直接设置

    5. @ConfigurationProperties:https://blog.csdn.net/qq_31854907/article/details/121470769

    6. 总结:bean后置处理器,和@Value类似都是从PropertySource获取,然后绑定到bean里

    7. @RefreshScope:https://blog.csdn.net/luqiang81191293/article/details/106678065

    8. 总结:使用@Scope做了代理,正常使用时会使用RefreshScope父类里缓存的Bean,可执行清理,清理后会重新创建Bean达到刷新的目的,apollo里@ConfigurationProperties需要用户添加@RefreshScope+调用refresh()才能实现刷新

    9. ImportBeanDefinitionRegistrar:使用ImportBeanDefinitionRegistrar动态创建自定义Bean到Spring中

    10. 总结:在spring周期中用来处理自定义BeanDefinition,调用在Bean初始化之前,Apollo中在这个扩展点中加入的配置注册、变更监听等相关逻辑

    11. multimap:Java使用multimap数据结构_srping123的博客-CSDN博客_java multimap

    相关文章

      网友评论

        本文标题:Apollo(4) Client包

        本文链接:https://www.haomeiwen.com/subject/rlktqdtx.html