想要学习携程的Apollo框架?这篇绝对够了!

​最近的工作,需要基于Apollo做一套可以支持实时修改系统配置信息的参数校验框架。

Apollo的源码不久前曾经研究过,不过那时候写的笔记不知道放到哪了。因此,今天写篇文章,记录下Apollo的源码和相关核心逻辑。

1. 依赖包引入

<dependency>
    <groupId>com.ctrip.framework.apollo</groupId>
    <artifactId>red-apollo-client</artifactId>
</dependency>

由于我在parent工程中增加了<dependencyManangement>配置,因此在工程中就不用再指定版本号了。

2. 包结构

apollo-client包结构如下:

凡是和Spring Boot集成的包,首先要找的一定是/META-INF中的spring.factories文件。apollo-client中的该文件信息如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ctrip.framework.apollo.spring.boot.ApolloAutoConfiguration
org.springframework.context.ApplicationContextInitializer=\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer
org.springframework.boot.env.EnvironmentPostProcessor=\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer

核心类有两个:

  • ApolloAutoConfiguration:这个从名字就很好理解,它是基于Spring Boot的AutoConfiguration配置类。一般情况下,和模块功能相关的bean对象的实例化过程,都会由这个类负责。

  • ApolloApplicationContextInitializer:实现了ApplicationContextInitializer和EnvironementPostProcessor两个接口。

Spring Boot启动过程,会先调用EnvironmentPostProcessor.postProcessEnvironment方法,再调用ApplicationContextInitializer.initialize方法。也就是Spring Boot优先准备环境,再初始化容器。

3. 测试准备

一般Spring Boot应用可以直接使用@SpringBootTest注解进行测试,但这样加载的bean对象太多了,最好先编写一些测试辅助代码,便于对Apollo框架进行测试。

使用@RunWith(SpringRunner.class)进行单元测试和使用@SpringBootTest进行单元测试的最大区别,是前者的EnvironmentPostProcessor.postProcessEnvironment方法是不会被调用的。因此,我们需要继承ApolloApplicationContextInitializer完成部分逻辑的重写:

/**
 * @author jingxuan
 * @date 2021/1/16 2:42 下午
 */
public class JUnitApolloApplicationContextInitializer extends ApolloApplicationContextInitializer {
    public void initialize(ConfigurableApplicationContext context) {
        ConfigurableEnvironment environment = context.getEnvironment();
        RandomValuePropertySource.addToEnvironment(environment);
        this.postProcessEnvironment(environment, null);

        super.initialize(context);
    }
}

这个类首先向Environment中添加了RandomValuePropertySource 对象,这个对象是Spring Boot应用启动时由框架自动添加的,但JUnit测试框架启动时却没有添加。由于apollo框架需要使用到这个对象,所以需要先手动创建一个。

另外就是在调用ApolloApplicationContextInitializer.initialize方法之前,先调用了ApolloApplicationContextInitializer.postProcessEnvironment方法,这样单元测试执行的逻辑就和Spring Boot应用执行的逻辑一样了。

有了这个类之后,我们可以通过如下的方式编写仅和apollo相关的测试案例:

@RunWith(SpringRunner.class)
@TestPropertySource(properties = {"apollo.bootstrap.enabled=true", "app.id=risk-insight"})
@ContextConfiguration(initializers = JUnitApolloApplicationContextInitializer.class)
@ImportAutoConfiguration({
        ApolloAutoConfiguration.class,
        ConfigurationPropertiesAutoConfiguration.class,
        PropertyPlaceholderAutoConfiguration.class,
        ApolloJsonValueTest.ApolloJsonValueConfiguration.class
})
public class ApolloJsonValueTest {
    @Configuration
    static class ApolloJsonValueConfiguration {
        @Bean
        User user() {
            return new User("jingxuan");
        }
    }

    static class User {
        @Getter @Setter
        protected String userName;
        @Getter @Setter
        @ApolloJsonValue("${apollo.junit.test.address:{}}")
        protected Address address;
        public User(String userName) {
            this.userName = userName;
        }
    }

    static class Address {
        @Getter @Setter
        protected String city;
        @Getter @Setter
        protected String province;
    }

    @Autowired(required = false)
    User user;

    @Test
    public void shouldInitUserBeanWithApolloJsonProperty() {
        assertThat(user, is(notNullValue()));
        assertThat(user.getAddress(), is(notNullValue()));
        assertThat(user.getAddress().getCity(), is("Shanghai"));
        assertThat(user.getAddress().getProvince(), is("Shanghai"));
    }
}

这段代码的主要部分简单解释下:

  • @RunWith(SpringRunner.class):Spring框架JUnit测试时的注解

  • @TestPropertySource:注册两个inline类型的和apollo相关的配置属性

  • @ContextConfiguration:指定使用前面编写的ApplicationContextInitializer类加载ApplicationContext

  • @ImportAutoConfiguration:指定需要加载的配置类,和apollo直接相关的是ApolloAutoConfiguration类,其余的部分是Spring Boot框架自带的,这里略过不介绍了。

4. 源码解析(一)

简单的就以上面给的测试类debug下框架。

首先是触发ApolloApplicationContextInitializer.postProcessEnvironment方法,初始化相关的环境变量。

第一步,执行的是这段代码:

  void initializeSystemProperty(ConfigurableEnvironment environment) {
    for (String propertyName : APOLLO_SYSTEM_PROPERTIES) {
      fillSystemPropertyFromEnvironment(environment, propertyName);
    }
  }

APOLLO_SYSTEM_PROPERTIES的内容如下:

fillSystemPropertyFromEnvironment方法的作用是将Envionment中相同名称的配置写入到系统环境变量中。

所以在Spring Boot中规定的可以用来做系统属性配置的地方,只要配置了这些属性值,就会通过这段代码,被写入到系统环境变量中。

第二步,根据参数APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED判断是否需要提前初始化apollo。这个APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED参数默认是false,也就是不会提前初始化。

public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {
    ...
    Boolean eagerLoadEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED, Boolean.class, false);
    //EnvironmentPostProcessor should not be triggered if you don't want Apollo Loading before Logging System Initialization
    if (!eagerLoadEnabled) {
      return;
    }
    Boolean bootstrapEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false);
    if (bootstrapEnabled) {
      initialize(configurableEnvironment);
    }
  }

在完成ApolloApplicationContextInitializer.postProcessEnvironment方法的执行后,会继续执行 ApolloApplicationContextInitializer.initialize方法。

第一步,判断是否需要初始化apollo框架:

  public void initialize(ConfigurableApplicationContext context) {
    ConfigurableEnvironment environment = context.getEnvironment();

    String enabled = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "false");
    if (!Boolean.valueOf(enabled)) {
      logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${{}}", context, PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
      return;
    }
    logger.debug("Apollo bootstrap config is enabled for context {}", context);

    initialize(environment);
  }

常量APOLLO_BOOTSTRAP_ENABLED对应的key值是apollo.bootstrap.enabled。

第二步,判断是否已经初始化了。因为在前面有一个eagerLoadEnabled的开关设置,所以肯定是需要增加这层校验的。

    if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
      //already initialized
      return;
    }

第三步,获取系统配置的namespaces列表,namespaces在apollo中用于对配置信息进行结构化管理。

    String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
    logger.debug("Apollo bootstrap namespaces: {}", namespaces);
    List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);

第四步,初始化到apollo服务器的连接并首次拉取配置文件,将配置作为一个PropertySource加载到Environment中,位置是在random之后。这也就是为什么我们一定要在Environment中实例化random属性源的原因了。

    CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
    for (String namespace : namespaceList) {
      Config config = ConfigService.getConfig(namespace);

      composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
    }

    environment.getPropertySources().addAfter("random",composite);

下面这张图是生成的CompositePropertySource对象结构:

主要关注几点:

  • PropertySource的名称叫:ApolloBoostrapPropertySources

  • 内部保存的属性源是一个列表,名称叫:propertySources

  • 列表中每个元素对应一个namespace,类型是ConfigPropertrySource

  • 元素中的m_configProperties中是服务器端属性配置的全量集合。

到这里如果一切正常的话,apollo-client就已经和apollo服务端建立连接了。

5. ApolloAutoConfiguration

现在需要详细介绍下ApolloAutoConfiguration这个类。

这个类的代码很短,直接贴一下:

@Configuration
@ConditionalOnProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED)
@ConditionalOnMissingBean(PropertySourcesProcessor.class)
public class ApolloAutoConfiguration {

  @Bean
  public ConfigPropertySourcesProcessor configPropertySourcesProcessor() {
    return new ConfigPropertySourcesProcessor();
  }
}

类定义上的注解比较正常,都是Spring Boot中常用到的,不过多解释了。

代码中可以看出来,主要就是创建了一个ConfigPropertySourcesProcessor类。

6. ConfigPropertySourcesProcessor

首先看下这个类的定义:

public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor
    implements BeanDefinitionRegistryPostProcessor
  • BeanDefinitionRegistryPostProcessor:这是Spring框架中定义的接口,其方法postProcessBeanDefinitionRegistry会在BeanFactory对象创建完成后,bean对象实例化前被调用

7. PropertySourcesProcessor

PropertySourcesProcessor这个是apollo中创建的类,类定义如下:

public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered
  • BeanFactoryPostProcessor:这也是Spring框架中定义的接口,其方法postProcessBeanFactory在BeanFactory对象创建完成,bean对象实例化之前被调用。

  • EnvironmentAware:这个简单,就是用来注入Spring的Environment对象的

  • PriorityOrdered:排序用的,不过多解释。

准确来说,BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry是作用在BeanDefinitionRegistry对象上的,而BeanFactoryPostProcessor.postProcessBeanFactory是作用在ConfigurableListableBeanFactory对象上的。不过由于Spring框架中实际这两个接口都会由一个类型为DefaultListableBeanFactory的类实现。调用顺序是先调用BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry,之后再调用BeanFactoryPostProcessor.postProcessBeanFactory。所以,上面的这种说法也没错。

DefaultListableBeanFactory类的继承链:

8. 源码解析(二)

ApolloAutoConfiguration会创建ConfigPropertySourcesProcessor对象,这个前面代码已经展示过了,不过多解释。

之后,ConfigPropertySourcesProcessor.postProcessBeanDefinitionRegistry会被调用:

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    helper.postProcessBeanDefinitionRegistry(registry);
  }

helper是DefaultConfigPropertySourcesProcessorHelper类型的对象,它的如下方法会被执行:

  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
    // to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer
    propertySourcesPlaceholderPropertyValues.put("order", 0);

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

    processSpringValueDefinition(registry);
  }

这段方法的逻辑:

第一步:注册PropertySourcesPlaceholderConfigurer对象,这是个Spring框架中的类,用于处理bean中和属性相关的placeholder,不多解释;

第二步:注册ApolloAnnotationProcessor对象,这是apollo中用于处理注解的处理器。简单解释下:

首先看下类继承关系:

  • BeanPostProcessor接口,说明这些对象在每一个bean被实例化的时候都有可能会被调用

再来看下ApolloProcessor这个关键类:

前两个方法来源自BeanPostProcessor,Spring框架在所有bean实例化时都会触发调用这两个方法;

中间两个方法看名字,是用来处理每一个bean对象的每一个属性和方法。

综合来看,ApolloProcessor可以根据需要,对bean中每一个方法和属性进行处理。

现在再来看ApolloAnnotationProcessor就好理解了,给出其processField方法的实现:

  @Override
  protected void processField(Object bean, String beanName, Field field) {
    ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class);
    if (annotation == null) {
      return;
    }

    Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()),
        "Invalid type: %s for field: %s, should be Config", field.getType(), field);

    String namespace = annotation.value();
    Config config = ConfigService.getConfig(namespace);

    ReflectionUtils.makeAccessible(field);
    ReflectionUtils.setField(field, bean, config);
  }

它对bean对象中使用@ApolloConfig注解进行标注的属性,进行了值注入。

那processMethod方法肯定是对方法进行处理了,不多解释。

第三步:注册SpringValueProcessor对象,它是用来处理Bean对象中的@Value注解的。

第四步:注册ApolloJsonValueProcessor对象,它是用来处理Bean对象中的@ApolloJsonValue注解的。

第五步:看起来是给处理流程打补丁的,手动触发执行一次SpringValueDefinitionProcessor.postProcessBeanDefinitionRegistry方法。

到这里,BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry的逻辑就执行完了,其中具体的细节不过多介绍,想了解的需要自己去阅读源代码,但整个大框架基本就这些。

此后,按照Spring框架的生命周期,会执行到BeanFactoryPostProcessor.postProcessBeanFactory方法,代码如下:

  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    initializePropertySources();
    initializeAutoUpdatePropertiesFeature(beanFactory);
  }

initializePropertySources的方法源码如下:

 private void initializePropertySources() {
    if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) {
      //already initialized
      return;
    }
    CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME);

    //sort by order asc
    ImmutableSortedSet<Integer> orders = ImmutableSortedSet.copyOf(NAMESPACE_NAMES.keySet());
    Iterator<Integer> iterator = orders.iterator();

    while (iterator.hasNext()) {
      int order = iterator.next();
      for (String namespace : NAMESPACE_NAMES.get(order)) {
        Config config = ConfigService.getConfig(namespace);

        composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
      }
    }

    // clean up
    NAMESPACE_NAMES.clear();

    // add after the bootstrap property source or to the first
    if (environment.getPropertySources()
        .contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {

      // ensure ApolloBootstrapPropertySources is still the first
      ensureBootstrapPropertyPrecedence(environment);

      environment.getPropertySources()
          .addAfter(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, composite);
    } else {
      environment.getPropertySources().addAfter(RANDOM_PROPERTY_SOURCE_NAME,composite);
    }
  }

第一步:判断是否已经加载过APOLLO_PROPERTY_SOURCE_NAME配置信息,这个APOLLO_PROPERTY_SOURCE_NAME的值是ApolloPropertySources。

这里很容易产生疑惑,因为这段代码好像见过。没错,在ApolloApplicationContextInitializer中有过相似的处理,不过名称其实是不一样的。那个地方处理的名字叫ApolloBootstrapPropertySources,多了个Bootstrap,说明是在不同阶段加载的属性。

第二步:加载NAMESPACE_NAMES中所有的配置信息到CompositePropertySource中;

第三步:将新创建的CompositePropertySource加入Environment中,位置在ApolloBootstrapPropertySources之后。

initializeAutoUpdatePropertiesFeature这个方法的作用是开启配置的自动更新特性,源代码如下:

  private void initializeAutoUpdatePropertiesFeature(ConfigurableListableBeanFactory beanFactory) {
    if (!configUtil.isAutoUpdateInjectedSpringPropertiesEnabled() ||
        !AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.add(beanFactory)) {
      return;
    }

    AutoUpdateConfigChangeListener autoUpdateConfigChangeListener = new AutoUpdateConfigChangeListener(
        environment, beanFactory);

    List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();
    for (ConfigPropertySource configPropertySource : configPropertySources) {
      configPropertySource.addChangeListener(autoUpdateConfigChangeListener);
    }
  }

AutoUpdateConfigChangeListener这个类用于监听apollo服务器端的配置更改的,如果有配置更改,则该类的onChange方法会被触发。

在apollo中可以查看一个名为springValueRegistry的对象,以确认哪些值的更新会触发刷新操作,这个springValueRegistry是使用Guice维护的一个单例。

代码如下:

this.springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class);

好了,如果上面这些你都看懂了的话,apollo框架的任何细节对于你来说都不会再是秘密了。整体而言,我觉得在国内的开源框架中,apollo整体的代码质量算是比较高的了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镜悬xhs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值