热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

携程Apollo配置中心动态生效实现原理

结论:Apollo配置中心动态生效机制,是基于Http长轮询请求和Spring扩展机制实现的,在Spring容器启动过程中,Apollo通过自定

结论:

Apollo配置中心动态生效机制,是基于Http长轮询请求和Spring扩展机制实现的,在Spring容器启动过程中,Apollo通过自定义的BeanPostProcessor和BeanFactoryPostProcessor將参数中包含${…}占位符和@Value注解的Bean注册到Apollo框架中定义的注册表中。然后通过Http长轮询不断的去获取服务端的配置信息,一旦配置发生变化,Apollo会根据变化的配置的Key找到对应的Bean,然后修改Bean的属性,从而实现了配置动态生效的特性。

需要注意的是,Apollo在配置变化后,只能修改Bean的属性,例如我们数据源的属性发生变化,新创建的Connection对象是没问题的,但是连接池中已经创建的Connection对象相关信息是不能动态修改的,所以依然需要重启应用。

 

Apollo配置中心动态生效原理
Spring中的重要概念
Spring框架启动过程回顾
Apollo原理解析
自定义BeanFactoryPostProcessor
自定义BeanPostProcessor
总结
Spring中的重要概念
在了解Apollo配置中心实现原理之前,我们需要先熟悉一下Spring框架中的几个重要的概念:
1、BeanDefinition
用于描述Bean的配置信息,Bean配置一般有三种方式:
(1)XML配置文件
(2)@Service、@Component等注解
(3)Java Config方式    
对应的BeanDefinition实现类如下图,Spring容器启动时,会把所有的Bean配置信息转换为BeanDefinition对象。

2、BeanDefinitionRegistry
BeanDefinition容器,所有的Bean定义都注册在BeanDefinitionRegistry对象中。

3、PropertySource
用于存放Spring配置资源信息,例如spring项目中properties或者yaml文件配置信息均会保存在PropertySource对象中。Spring支持使用@PropertySource注解,將配置信息加载到Environment对象中。

4、ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar是一个接口,该接口的实现类作用于在Spring解析Bean配置生成BeanDefinition对象阶段。
在Spring解析Configuration注解时,向Spring容器中增加额外的BeanDefinition。

5、BeanFactoryPostProcessor
Bean工厂后置处理器,用于在BeanDefinition对象注册完成后,修改Bean工厂信息,例如增加或者修改BeanDefinition对象。

6、BeanDefinitionRegistryPostProcessor
它是一个特殊的BeanFactoryPostProcessor,用于在BeanDefinition对象注册完成后,访问、新增或者修改BeanDefinition信息。

7、PropertySourcesPlaceholderConfigurer
它是一个特殊的BeanFactoryPostProcessor,用于解析Bean配置中的${…}参数占位符。

8、BeanPostProcessor
Bean后置处理器,bean初始化方法调用前后,执行拦截逻辑,可以对原有的Bean进行包装或者根据标记接口创建代理对象。

Spring框架启动过程回顾
Spring框架启动大致会经过以下几个阶段:
1、解析Bean配置信息,將配置信息转换为BeanDefinition对象,注册到BeanDefinitionRegistry中。

2、执行所有的BeanFactoryPostProcessor的postProcessBeanFactory()方法对Bean工厂信息进行修改,包括修改或新增BeanDefinition对象。

注意:如果需要控制BeanFactoryPostProcessor的执行顺序需要实现PriorityOrdered接口,getOrder()方法返回的值越小,执行优先级越高。

3、通过BeanDefinition对象实例化所有Bean,注入依赖。

4、执行所有BeanPostProcessor对象的postProcessBeforeInitialization()方法。

5、执行Bean的初始化方法,例如InitializingBean接口的afterPropertiesSet方法,或init-method属性指定的方法。

执行所有BeanPostProcessor对象的postProcessAfterInitialization()方法

Apollo原理解析
Apollo框架使用非常简单,如果是Spring Boot项目,只需要在启动类上增加@EnableApolloConfig注解即可。例如:

@SpringBootApplication
@EnableApolloConfig
public class Application {

    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        SpringApplication.run(Application.class, args);
    }
}
1
2
3
4
5
6
7
8
9
那么@EnableApolloConfig注解到底做了什么事情了,我们可以看下EnableApolloConfig注解的定义,代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ApolloConfigRegistrar.class)
public @interface EnableApolloConfig {
  /**
   * Apollo namespaces to inject configuration into Spring Property Sources.
   */
  String[] value() default {ConfigConsts.NAMESPACE_APPLICATION};

  /**
   * The order of the apollo config, default is {@link Ordered#LOWEST_PRECEDENCE}, which is Integer.MAX_VALUE.
   * If there are properties with the same name in different apollo configs, the apollo config with smaller order wins.
   * @return
   */
  int order() default Ordered.LOWEST_PRECEDENCE;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
如上面代码所示,在EnableApolloConfig注解中,通过@Import注解导入了一个ApolloConfigRegistrar,接下来我们就来看一下ApolloConfigRegistrar的实现:

public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata
        .getAnnotationAttributes(EnableApolloConfig.class.getName()));
    String[] namespaces = attributes.getStringArray("value");
    int order = attributes.getNumber("order");
    PropertySourcesProcessor.addNamespaces(Lists.newArrayList(namespaces), order);

    Map propertySourcesPlaceholderPropertyValues &#61; new HashMap<>();
    // to make sure the default PropertySourcesPlaceholderConfigurer&#39;s priority is higher than PropertyPlaceholderConfigurer
    propertySourcesPlaceholderPropertyValues.put("order", 0);

    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
        PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);

    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(),
        PropertySourcesProcessor.class);

    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
        ApolloAnnotationProcessor.class);

    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class);

    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
            ApolloJsonValueProcessor.class);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
如上面代码所示&#xff0c;ApolloConfigRegistrar实现了ImportBeanDefinitionRegistrar接口&#xff0c;前面有提到过&#xff0c;ImportBeanDefinitionRegistrar接口的实现类作用于在Spring解析Bean配置生成BeanDefinition对象阶段&#xff0c;在Spring解析Configuration注解时&#xff0c;向Spring容器中增加额外的BeanDefinition。

ApolloConfigRegistrar中注册了几个BeanDefinition&#xff0c;具体如下&#xff1a;
1、PropertySourcesPlaceholderConfigurer -------->BeanFactoryPostProcessor
2、PropertySourcesProcessor -------->BeanFactoryPostProcessor
3、ApolloAnnotationProcessor -------->BeanPostProcessor
4、SpringValueProcessor -------->BeanFactoryPostProcessor和BeanPostProcessor
5、SpringValueDefinitionProcessor-------->BeanDefinitionRegistryPostProcessor
&#xff08;即BeanFactoryPostProcessor&#xff09;
6、ApolloJsonValueProcessor -------->BeanPostProcessor

这些类要么实现了BeanFactoryPostProcessor接口&#xff0c;要么实现了BeanPostProcessor接口&#xff0c;前面有提到过BeanFactoryPostProcessor和BeanPostProcessor是Spring提供的扩展机制&#xff0c;BeanFactoryPostProcessor一定是在BeanPostProcessor之前执行。

接下来我们就来看一下这些自定义的BeanFactoryPostProcessor和BeanPostProcessor的执行顺序&#xff0c;以及它们具体做了什么事情。

自定义BeanFactoryPostProcessor
1、SpringValueDefinitionProcessor

对所有的BeanDefinition进行遍历&#xff0c;將属性中包含${…}参数占位符的属性添加到Apollo 属性注册表。Apollo 属性注册表具体结构如下&#xff1a;

2、PropertySourcesProcessor
&#xff08;1&#xff09;根据命名空间从配置中心获取配置信息&#xff0c;创建RemoteConfigRepository和LocalFileConfigRepository对象。RemoteConfigRepository表示远程配置中心资源&#xff0c;LocalFileConfigRepository表示本地缓存配置资源。

&#xff08;2&#xff09;LocalFileConfigRepository对象缓存配置信息到C:\opt\data 或者/opt/data目录。

&#xff08;3&#xff09;RemoteConfigRepository开启HTTP长轮询请求定时任务&#xff0c;默认2s请求一次。

&#xff08;4&#xff09;將本地缓存配置信息转换为PropertySource对象&#xff08;Apollo自定义了Spring的PropertySource&#xff09;&#xff0c;加载到Spring的Environment对象中。

&#xff08;5&#xff09;將自定义的ConfigPropertySource注册为观察者。一旦RemoteConfigRepository发现远程配置中心信息发生变化&#xff0c;ConfigPropertySource对象会得到通知。

3、PropertySourcesPlaceholderConfigurer
加载本地Properties文件&#xff0c;將${…}参数占位符替换为具体的值。

4、SpringValueProcessor
仅仅是为了获取SpringValueDefinitionProcessor中获取的 包含${…}参数占位符的BeanDefinition。&#xff08;从面向对象设计原则的角度&#xff0c;不符合单一责任原则&#xff0c;可以注册到Guice容器里&#xff0c;然后从Guice容器获取。&#xff09;

自定义BeanPostProcessor
5、ApolloJsonValueProcessor
处理ApolloJsonValue注解&#xff0c;属性或者方法中包含ApolloJsonValue注解的Bean&#xff0c;属性值也会根据配置中心配置的修改发生变化&#xff0c;因此也需要添加到配置中心可配的容器中

6、ApolloAnnotationProcessor
处理ApolloConfigChangeListener注解&#xff0c;ApolloConfigChangeListener注解用于注册一个配置变化监听器。

7、SpringValueProcessor
处理Spring中的Value注解&#xff0c;將属性或者方法中包含Value注解的Bean信息添加到Apollo属性注册表。

整个过程如下图所示&#xff1a;


总结
Apollo配置中心动态生效机制&#xff0c;是基于Http长轮询请求和Spring扩展机制实现的&#xff0c;在Spring容器启动过程中&#xff0c;Apollo通过自定义的BeanPostProcessor和BeanFactoryPostProcessor將参数中包含${…}占位符和&#64;Value注解的Bean注册到Apollo框架中定义的注册表中。然后通过Http长轮询不断的去获取服务端的配置信息&#xff0c;一旦配置发生变化&#xff0c;Apollo会根据变化的配置的Key找到对应的Bean&#xff0c;然后修改Bean的属性&#xff0c;从而实现了配置动态生效的特性。

需要注意的是&#xff0c;Apollo在配置变化后&#xff0c;只能修改Bean的属性&#xff0c;例如我们数据源的属性发生变化&#xff0c;新创建的Connection对象是没问题的&#xff0c;但是连接池中已经创建的Connection对象相关信息是不能动态修改的&#xff0c;所以依然需要重启应用。
 


推荐阅读
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文讨论了在Spring 3.1中,数据源未能自动连接到@Configuration类的错误原因,并提供了解决方法。作者发现了错误的原因,并在代码中手动定义了PersistenceAnnotationBeanPostProcessor。作者删除了该定义后,问题得到解决。此外,作者还指出了默认的PersistenceAnnotationBeanPostProcessor的注册方式,并提供了自定义该bean定义的方法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • springmvc学习笔记(十):控制器业务方法中通过注解实现封装Javabean接收表单提交的数据
    本文介绍了在springmvc学习笔记系列的第十篇中,控制器的业务方法中如何通过注解实现封装Javabean来接收表单提交的数据。同时还讨论了当有多个注册表单且字段完全相同时,如何将其交给同一个控制器处理。 ... [详细]
  • 标题: ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
author-avatar
我叫梁家耀_312
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有