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:
- With class loading ApolloConfigRegistrarHelper SPI record mechanism
This step was added in 19, presumably to introduce SPI with custom load registration classes
- 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
- 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