For a reference to the source code, please visit: gitee.com/lidishan/ap… Note before reading: This article does not do related instructions, only parsing apollo-client source code

Apollo has a note, so let’s look at this startup note

@EnableApolloConfig

Why use this annotation?

System startup, retrieves the corresponding XML configuration information, and pull the Apollo data covered in postProcessAfterInitialization phases

The configuration annotation initialization process is as follows:

Step 1: Add the @enableApolloConfig annotation to the startup class

@EnableApolloConfig
/ /... Other annotations
public class Boot {
    public static void main(String[] args) {
        newSpringApplication(Boot.class).run(args); }}Copy the code

Step 2: point into the annotation, in @ Import (ApolloConfigRegistrar. Class) injected into this class bean container

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ApolloConfigRegistrar.class) / / @ Import (ApolloConfigRegistrar. Class) injected into this class bean container
public @interface EnableApolloConfig {
  /** Inject the spring property namespace, default: application */
  String[] value() default {ConfigConsts.NAMESPACE_APPLICATION};
  /** order */
  int order(a) default Ordered.LOWEST_PRECEDENCE;
}
Copy the code

The ApolloConfigRegistrar will be injected into the bean container and the general logic is as follows:

  1. With class loading ApolloConfigRegistrarHelper SPI record mechanism

This step was added in 19, presumably to introduce SPI with custom load registration classes

  1. PropertySourcesProcessor configuration initialization (it also provides the onChange method for automatically updating properties)

— Registers a listener method, which takes a value from springValueRegistry when triggered, and then finds the corresponding bean-related information reflection for reassignment

  1. Register all inherited ApolloProcessor interface bean (ApolloProcessor there are postProcessBeforeInitialization inside

, postProcessAfterInitialization both in execution of hook) before and after the initialization, and then realize the template method, inside the hook and scan the corresponding notes by using the assignment operator

Instantiated beans are as follows:

ApolloAnnotationProcessor: -1 Get annotation @apolloconfig via reflection and parse the value, Then reflection into the corresponding bean field – 2 will be declared the @ ApolloConfigChangeListener annotation method added to the corresponding Namespace Namespace configuration SpringValueProcessor: Parsing is @ the field and the method of the Value statement, and then put the bean and key information such as packing in SpringValueRegistry SpringValueDefinitionProcessor: Find the value information for all bean mappings and store it in springValueDefinitions. For example: mybatis initialization class parameters and XML mapping attribute ApolloJsonValueProcessor: scan all annotations and load the assignment into them. This annotation makes it easier to parse json

Source code analysis steps are as follows:

1. SPI loading. Can be in outer custom inherited ApolloConfigRegistrarHelper implementation class, and then add a @ Order annotations to the largest, can override the default loading way, into his way of loading

Load (clazz) spi class load(clazz) spi class load(clazz) spi class load(clazz) SPI
private ApolloConfigRegistrarHelper helper = ServiceBootstrap.loadPrimary(ApolloConfigRegistrarHelper.class);
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    helper.registerBeanDefinitions(importingClassMetadata, registry);
}
Copy the code

2. The PropertySourcesProcessor configuration is initialized (it also provides the onChange method for automatically updating properties)

— Registers a listener method, which takes a value from springValueRegistry when triggered, and then finds the corresponding bean-related information reflection for reassignment

/ / invocation path: DefaultApolloConfigRegistrarHelper - > PropertySourcesProcessor
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){
    // PropertySourcesProcessor: configuration initialization (there is also an onChange method that automatically updates properties)
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(), PropertySourcesProcessor.class);
}
public class PropertySourcesProcessor implements BeanFactoryPostProcessor.EnvironmentAware.PriorityOrdered {
  / /...
  / * * * * /
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    // Initializes the property configuration
    initializePropertySources();
    // The initialization property is automatically updated
    initializeAutoUpdatePropertiesFeature(beanFactory);
  }
  / /...
}
/** * listen for changes and trigger this event if configuration changes */
@Override
public void onChange(ConfigChangeEvent changeEvent) {
    Set<String> keys = changeEvent.changedKeys();
    if (CollectionUtils.isEmpty(keys)) {
        return;
    }
    for (String key : keys) {
        // 1. check whether the changed key is relevant
        // Take the value from springValueRegistry, find the corresponding bean instance, and reset the value by reflection
        Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
        if (targetValues == null || targetValues.isEmpty()) {
            continue;
        }
        // 2. update the value
        for(SpringValue val : targetValues) { updateSpringValue(val); }}}Copy the code

3. Register all inherited ApolloProcessor interface bean (ApolloProcessor there are postProcessBeforeInitialization inside

, postProcessAfterInitialization both in execution of hook) before and after the initialization, and then realize the template method, inside the hook and scan the corresponding notes by using the assignment operator

/ / invocation path: DefaultApolloConfigRegistrarHelper - > PropertySourcesProcessor
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    / /... Other logic
    / / = = = = = = = = = = = = = = = = the following class have achieved ApolloProcessor = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    / / = = = = = = = = = = = = = = = = = = = = = = = = = = postProcessBeforeInitialization, postProcessAfterInitialization inside,
    / / = = = = = = = = = = = = = = = = = = = = = = = = = = inside the template method pattern, the only need to implement the corresponding template method processField (), processMethod ()
    // -1 gets the annotation @apolloConfig by reflection, parses the fetched value, and then reflects the field of the corresponding bean
    / / - 2 will be declared the @ ApolloConfigChangeListener annotation method to add into the corresponding Namespace Namespace configuration
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(), ApolloAnnotationProcessor.class);
    // -1 parses the fields and methods declared by @value and wraps the bean and key information into SpringValueRegistry
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
    // -1 find the value information for all bean mappings and store it in springValueDefinitions. For example: mybatis initialization class parameters and XML mapping attributes
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class);
    // -1 scans all annotations and loads assignments into them. This annotation makes it easier to parse json
    // @ApolloJsonValue("${someJsonPropertyKey:{\"someString\":\"someDefaultValue\", \"someInt\":10}}")
    // private SomeObject someObject;
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(), ApolloJsonValueProcessor.class);
    / /... Other logic
}
Copy the code

Note: If you look carefully, you’ll notice that the onChange() method only loads data from springValueRegistry. And SpringValueDefinitionProcessor registered bean is not store data in springValueRegistry (we can think of it as a kind of annotation by mybatis. XML configuration such as analytical method). In this case, Apollo doesn’t overwrite its value

In addition, Apolloc-client implements packages with the SpringBoot automatic loading mechanism. Its initialization is as follows

What does spring.factories load with springBoot

# If there are no Apollo annotations, this will be used to initialize some scans, but what is the difference between this and Apollo annotations
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ctrip.framework.apollo.spring.boot.ApolloAutoConfiguration
Initialize context parameters (load context parameters and store them)
org.springframework.context.ApplicationContextInitializer=\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer
-dapollo.meta =12, app.properties, etc.
org.springframework.boot.env.EnvironmentPostProcessor=\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer
Copy the code

Step 2: Take a look at the auto assembly class ApolloAutoConfiguration

1. AutoConfiguration will only work if (Apollo.bootstrap. enabled=true && bean=PropertySourcesProcessor is not initialized)

// This autoConfiguration is not initialized only in apollo.bootstrap.enabled=true && bean=PropertySourcesProcessor
// Autoassemble time node is later than @enableApolloConfig time node
@Configuration
@ConditionalOnProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED)// Apoll.bootstrap. enabled=true
@ConditionalOnMissingBean(PropertySourcesProcessor.class)// This bean=PropertySourcesProcessor is not initialized to trigger initialization here. Used to contradict the annotated form. When no annotations are used, the current auto-configuration class initialization is triggered
public class ApolloAutoConfiguration {
    @Bean
    public ConfigPropertySourcesProcessor configPropertySourcesProcessor(a) {
        return newConfigPropertySourcesProcessor(); }}Copy the code

2. If implemented on autoConfiguration no effect, and can trigger ApplicationContextInitializer and EnvironmentPostProcessor effect,

Both initialization and load in the Apollo environment configuration is in the same class: ApolloApplicationContextInitializer

Analyze the ApolloApplicationContextInitializer below picture is as follows:

  • Core functions of this class:
    • Initialize Apollo system configuration initialize(). Iterate through the namespace, calling configService.getConfig () to load all Apollo configurations
    • Inject Apollo configuration information postProcessEnvironment(). Initialize environment variables, which are executed before initialize()
/ * * * initializes the Apollo system configuration, the initialize () and injection of Apollo configuration information postProcessEnvironment () * - ApplicationContextInitializer is a injection point, */ is called before the Spring container is initialized
public class ApolloApplicationContextInitializer implements
    ApplicationContextInitializer<ConfigurableApplicationContext>,EnvironmentPostProcessor.Ordered {
  public static final int DEFAULT_ORDER = 0;

  private static final Logger logger = LoggerFactory.getLogger(ApolloApplicationContextInitializer.class);
  private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
  private static final String[] APOLLO_SYSTEM_PROPERTIES = {"app.id", ConfigConsts.APOLLO_CLUSTER_KEY,
      "apollo.cacheDir"."apollo.accesskey.secret", ConfigConsts.APOLLO_META_KEY, PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE};

  private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector
      .getInstance(ConfigPropertySourceFactory.class);

  private int order = DEFAULT_ORDER;

  /** * This interface is called before the container is initialized * - get context * - Initialize loads environment data */
  @Override
  public void initialize(ConfigurableApplicationContext context) {
    // Get environment variable information
    ConfigurableEnvironment environment = context.getEnvironment();
    // This is used to configure whether to enable, the default is enabled. False disables this function
    if(! environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class,false)) {
      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);
  }

  /** * After the environment variables are ready, initialize the configuration (that is, traverse all namespaces and log through configService.getConfig ()) */
  protected void initialize(ConfigurableEnvironment environment) {

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

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

    // The configService.getConfig is executed and the call is initialized with a long poll + timed five-minute pull
    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().addFirst(composite);
  }

  /** * Load environment variables such as Apollo configuration information - dapollo.meta */
  @Override
  public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {
    // should always initialize system properties like app.id in the first place
    initializeSystemProperty(configurableEnvironment);
    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); }}/ /... Omit other logic
}
Copy the code