引言
为了在项目中用好框架,以及出现问题时候能够快速定位、分析、优化,文章尝试从源码角度分析Spring集成apollo的过程。期望文章能够把以下几个事情描述清楚:
- apollo通过使用Spring哪些扩展点,完成了与Spring的集成;
- apollo中的配置如何融入到Spring Environment;
- apollo中的配置项如何赋值给Spring Bean相关字段、方法;
- 在应用运行过程中,当修改apollo中的配置,配置如何在Spring Bean相关字段、方法上生效的
由于Spring Framework和Spring Boot集成apollo的方式有些许不同,分别进行分析。
Spring Framework
示例
Spring XML配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:apollo="http://www.ctrip.com/schema/apollo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd" > <apollo:config namespaces="application,public_namespace"/> <context:component-scan base-package="test.springframework"/> <bean id="test" class="test.springframework.Test"> <property name="test1" value="${test1}"/> </bean> </beans>
- apollo:config配置的是apollo app.id中的命令空间(app.id、app.meta配置可以参考apollo官方文档);
- context:component-scan配置的是Spring component注解的扫描路径;
- 另一个重要的配置是xmlns:apollo="http://www.ctrip.com/schema/apollo",后面内容会有分析。
Spring Bean
package test.springframework; public class Test { @ApolloConfig private Config config; @Value("${test2:}") private String test2; private String test1; public String getTest1() { return test1; } public void setTest1(String test1) { this.test1 = test1; } @ApolloConfigChangeListener public void onChange(ConfigChangeEvent changeEvent){ System.out.println(changeEvent); } }
集成分析
图中概括地描述了refresh过程,其中标识黄颜色的地方是这次分析的重点,下面分别进行描述。
解析apollo:config
当解析xml文件apollo:config标记的时候调用BeanDefinitionParserDelegate.parseCustomElement(…),主要流程如下:
图中流程主要完成了两件事:
- 将xml中配置的apollo命名空间存储到了PropertySourcesProcessor(属于apollo jar包)类中的NAMESPACE_NAMES字段;
- 将ConfigPropertySourcesProcessor(属于apollo jar包)作为BeanDefinition注册到了Spring容器中;ConfigPropertySourcesProcessor实现了Spring接口BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor。
调用BeanFactoryPostProcessor
apollo中ConfigPropertySourcesProcessor实现了Spring接口BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor,ConfigPropertySourcesProcessor实现如下:
public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor implements BeanDefinitionRegistryPostProcessor { //使用java SPI机制,ConfigPropertySourcesProcessorHelper对应的实现类是DefaultConfigPropertySourcesProcessorHelper,这段代码完成了apollo client初始化 private ConfigPropertySourcesProcessorHelper helper = ServiceBootstrap.loadPrimary(ConfigPropertySourcesProcessorHelper.class); @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { helper.postProcessBeanDefinitionRegistry(registry); } }
调用BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry
实际调用的是DefaultConfigPropertySourcesProcessorHelper.postProcessBeanDefinitionRegistry方法,该方法主要完成以下几件事:
- 将PropertySourcesPlaceholderConfigurer注册到spring容器中,该类实现了接口BeanFactoryPostProcessor,用于占位符的转换处理;
- 将ApolloAnnotationProcessor注册到spring容器中,该类实现了接口BeanPostProcessor;
- 将SpringValueProcessor注册到spring容器中,该类实现了接口BeanFactoryPostProcessor和BeanPostProcessor;
- 将ApolloJsonValueProcessor注册到spring容器中,该类实现了接口BeanPostProcessor;
- 将BeanDefinition中带有占位符的所有属性存储到Map<BeanDefinitionRegistry, Multimap<String, SpringValueDefinition>>结构中
调用BeanFactoryPostProcessor.postProcessBeanFactory
PropertySourcesProcessor
CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME); 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)); } } NAMESPACE_NAMES.clear(); environment.getPropertySources().addFirst(composite);
- NAMESPACE_NAMES中存储的是应用配置的apollo命名空间;
- 通过Config config = ConfigService.getConfig(namespace)获取每个命名空间的配置对象,将Config对象封装成ConfigPropertySource,接着将所有ConfigPropertySource放入CompositePropertySource,最后将CompositePropertySource加入到spring ConfigurableEnvironment中,此时spring容器的ConfigurableEnvironment已经拥有了apollo命令空间的配置;
AutoUpdateConfigChangeListener autoUpdateConfigChangeListener = new AutoUpdateConfigChangeListener(environment, beanFactory); List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources(); for (ConfigPropertySource configPropertySource : configPropertySources) { configPropertySource.addChangeListener(autoUpdateConfigChangeListener); }
- 为apollo命名空间对象添加监听AutoUpdateConfigChangeListener,当改变命名空间中配置的时候,该监听完成Bean对象属性值得更新,及方法的调用。
PropertySourcesPlaceholderConfigurer
- 将ConfigurableEnvironment构造成自身的PropertySource;
new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) { public String getProperty(String key) { return this.source.getProperty(key); } }
- 通过自身的PropertySource构造PropertySourcesPropertyResolver,PropertySourcesPropertyResolver中完成解析的类是PropertyPlaceholderHelper;
new PropertySourcesPropertyResolver(this.propertySources)
- 构造StringValueResolver,然后调用PropertySourcesPropertyResolver来进行解析;
StringValueResolver valueResolver = strVal -> { String resolved = (ignoreUnresolvablePlaceholders ? propertyResolver.resolvePlaceholders(strVal) : propertyResolver.resolveRequiredPlaceholders(strVal)); if (trimValues) { resolved = resolved.trim(); } return (resolved.equals(nullValue) ? null : resolved); };
- 构造BeanDefinitionVisitor,用于解析BeanDefinition包含的所有String值(属性、构造方法参数、元数据),解析找到的任何占位符。
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver); public void visitBeanDefinition(BeanDefinition beanDefinition) { visitParentName(beanDefinition); visitBeanClassName(beanDefinition); visitFactoryBeanName(beanDefinition); visitFactoryMethodName(beanDefinition); visitScope(beanDefinition); if (beanDefinition.hasPropertyValues()) { visitPropertyValues(beanDefinition.getPropertyValues()); } if (beanDefinition.hasConstructorArgumentValues()) { ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues(); visitIndexedArgumentValues(cas.getIndexedArgumentValues()); visitGenericArgumentValues(cas.getGenericArgumentValues()); } } @Nullable protected String resolveStringValue(String strVal) { if (this.valueResolver == null) { throw new IllegalStateException("No StringValueResolver specified - xxx"); } // 最终调用的是PropertySourcesPropertyResolver中的resolveXXX方法 String resolvedValue = this.valueResolver.resolveStringValue(strVal); return (strVal.equals(resolvedValue) ? strVal : resolvedValue); }
调用BeanPostProcessor
ApolloAnnotationProcessor
用于解析apollo注解ApolloConfig、ApolloConfigChangeListener。
ApolloConfig
为注解为ApolloConfig的字段赋值对应的namespace Config对象。
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); }
ApolloConfigChangeListener
为注解为ApolloConfigChangeListener的方法添加namespace的监听。
ApolloConfigChangeListener annotation = AnnotationUtils .findAnnotation(method, ApolloConfigChangeListener.class); String[] namespaces = annotation.value(); ConfigChangeListener configChangeListener = new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { ReflectionUtils.invokeMethod(method, bean, changeEvent); } }; for (String namespace : namespaces) { Config config = ConfigService.getConfig(namespace); config.addChangeListener(configChangeListener); }
SpringValueProcessor
- 将Bean中含有Value注解的字段、方法注册到SpringValueRegistry(Map<BeanFactory, Multimap<String, SpringValue>>)中,SpringValue保存了Bean实例,对应的key,Field或Method;
protected void processField(Object bean, String beanName, Field field) { Value value = field.getAnnotation(Value.class); Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value()); for (String key : keys) { SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false); springValueRegistry.register(beanFactory, key, springValue); } } protected void processMethod(Object bean, String beanName, Method method) { Value value = method.getAnnotation(Value.class); Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value()); for (String key : keys) { SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false); springValueRegistry.register(beanFactory, key, springValue); } }
- 将DefaultConfigPropertySourcesProcessorHelper中解析的Map<BeanDefinitionRegistry, Multimap<String, SpringValueDefinition>>转换为SpringValue保存在SpringValueRegistry中;
- 前面在PropertySourcesProcessor类中对每个namespace Config对象注册了监听AutoUpdateConfigChangeListener,AutoUpdateConfigChangeListener类含有SpringValueRegistry的引用;
- 当namespace Config配置改变的时候会通知到AutoUpdateConfigChangeListener,AutoUpdateConfigChangeListener通过配置key找到对应的SpringValue对象,通过SpringValue改变Bean对应的属性,或调用Bean对应的方法。
ApolloJsonValueProcessor
- 将Bean中含有ApolloJsonValue注解的字段、方法注册到SpringValueRegistry(Map<BeanFactory, Multimap<String, SpringValue>>)中,SpringValue保存了Bean实例,对应的key,Field或Method;
- 对于注解在Field的情况,获取ApolloJsonValue key的配置,将配置转换为Field对应类型的对象并完成对Field的赋值;
protected void processField(Object bean, String beanName, Field field) { ApolloJsonValue apolloJsonValue = AnnotationUtils.getAnnotation(field, ApolloJsonValue.class); String placeholder = apolloJsonValue.value(); Object propertyValue = placeholderHelper .resolvePropertyValue(beanFactory, beanName, placeholder); boolean accessible = field.isAccessible(); field.setAccessible(true); ReflectionUtils .setField(field, bean, parseJsonValue((String)propertyValue, field.getGenericType())); field.setAccessible(accessible); if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeholder); for (String key : keys) { SpringValue springValue = new SpringValue(key, placeholder, bean, beanName, field, true); springValueRegistry.register(beanFactory, key, springValue); } } }
- 对于注解在Method的情况,获取ApolloJsonValue key的配置,将配置转换为Method入参对应类型的对象并完成对Method的调用
protected void processMethod(Object bean, String beanName, Method method) { ApolloJsonValue apolloJsonValue = AnnotationUtils.getAnnotation(method, ApolloJsonValue.class); String placeHolder = apolloJsonValue.value(); Object propertyValue = placeholderHelper .resolvePropertyValue(beanFactory, beanName, placeHolder); boolean accessible = method.isAccessible(); method.setAccessible(true); ReflectionUtils.invokeMethod(method, bean, parseJsonValue((String)propertyValue, types[0])); method.setAccessible(accessible); if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeHolder); for (String key : keys) { SpringValue springValue = new SpringValue(key, apolloJsonValue.value(), bean, beanName,method, true); springValueRegistry.register(beanFactory, key, springValue); } } }
Spring Boot
示例
// Spring Boot启动类 @SpringBootApplication @EnableApolloConfig public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } //测试Bean见Spring Framework部分示例
上面代码中的@EnableApolloConfig是一个接入点,后面会分析到。
集成分析
图中概括地描述了SpringApplication启动过程(其中会将启动类加入到spring容器中),其中标识黄颜色的地方是这次分析的重点,下面分别进行描述。
加载apollo ApplicationContextInitializer
- 通过SpringFactoriesLoader加载apollo jar包中META-INF/spring.factories文件,解析ApplicationContextInitializer实现类ApolloApplicationContextInitializer。
setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); // 通过SpringFactoriesLoader加载、解析META-INF/spring.factories private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Set<String> names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
apollo ApplicationContextInitializer initialize
- ApolloApplicationContextInitializer解析apollo.bootstrap.namespaces配置的命名空间名称,然后通过Config config = ConfigService.getConfig(namespace)获取每个命名空间的配置对象,将Config对象封装成ConfigPropertySource,接着将所有ConfigPropertySource放入CompositePropertySource,最后将CompositePropertySource加入到spring ConfigurableEnvironment中,此时spring容器的ConfigurableEnvironment已经拥有了apollo命令空间的配置。
protected void initialize(ConfigurableEnvironment environment) { String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION); List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces); 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); }
初始化BeanDefinitionLoader
我们关注BeanDefinitionLoader类中属性annotatedReader=new AnnotatedBeanDefinitionReader(registry),AnnotatedBeanDefinitionReader构造方法如下:
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) { this.registry = registry; this.conditionEvaluator = new ConditionEvaluator(registry, environment, null); // 将Annotation相关的处理类注册到Spring容器中,如: // ConfigurationClassPostProcessor : 处理Configuration注解 // AutowiredAnnotationBeanPostProcessor : 处理Value注解 // RequiredAnnotationBeanPostProcessor : 处理Required注解 // CommonAnnotationBeanPostProcessor : 处理PostConstruct/PreDestroy/Resource/Lazy注解 // ... ... AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); }
与我们这次分析相关的类是:ConfigurationClassPostProcessor,该类实现了BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor接口。
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry
- 判断spring容器中BeanDefinition是否含有Configuration、Component、ComponentScan、Import
ImportResource、Bean注解,没有则直接返回;
- 构造ConfigurationClassParser(Parses a Configuration class definition, populating a collection of ConfigurationClass objects (parsing a single Configuration class may result in any number of ConfigurationClass objects because one Configuration class may import another using the Import annotation)),接着对BeanDefinition上的Configuration、Component、ComponentScan、Import
ImportResource、Bean注解进行解析。
- 解析完成后,通过ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClass)进行apollo相关BeanDefinition的加载:
- apollo EnableApolloConfig注解的定义如下
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented // 在上面parse阶段会将Import封装在ConfigurationClass中 @Import(ApolloConfigRegistrar.class) public @interface EnableApolloConfig { String[] value() default {ConfigConsts.NAMESPACE_APPLICATION}; int order() default Ordered.LOWEST_PRECEDENCE; }
- ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars会调用到ApolloConfigRegistrar类的registerBeanDefinitions方法
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) { registrars.forEach((registrar, metadata) -> registrar.registerBeanDefinitions(metadata, this.registry)); }
- ApolloConfigRegistrar最终将apollo中BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor、BeanPostProcessor实现类的注册到spring容器中(具体实现类与Spring Framework部分基本是一致的),后续的逻辑与Spring Framework部分分析的是一致的了。
public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar { private ApolloConfigRegistrarHelper helper = ServiceBootstrap.loadPrimary(ApolloConfigRegistrarHelper.class); @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 该方法中将apollo相关类注册到spring容器中,如: // PropertySourcesProcessor // ApolloAnnotationProcessor // SpringValueProcessor // SpringValueDefinitionProcessor // ApolloJsonValueProcessor helper.registerBeanDefinitions(importingClassMetadata, registry); } }
总结
文章主要介绍了apollo借助Spring扩展点完成了与Spring的集成:
Spring Framework集成方式使用到了loadBeanDefinitions阶段中apollo:config NamespaceHandler,BeanDefinitionRegistryPostProcessor,BeanFactoryPostProcessor,BeanPostProcessor;
Spring Boot集成方式使用到了ApplicationContextInitializer,BeanDefinitionRegistryPostProcessor,BeanFactoryPostProcessor,BeanPostProcessor。