这里通过 Nacos 的 nacos-config-spring-boot-starter,来分析下 Nacos Config 在 Spring Boot 环境下的启动跟属性更新过程,版本是 0.2.7。
首先找到对应的 autoconfigure jar 包,找启动类,也就是com.alibaba.boot.nacos.config.autoconfigure.NacosConfigAutoConfiguration。整个的调用层次如下所示:
代码调用层次NacosConfigAutoConfiguration
NacosConfigAutoConfiguration 很简单,核心关注两个注解:@Import(value = { NacosConfigBootBeanDefinitionRegistrar.class }) 及 @EnableNacosConfig。
而@EnableNacosConfig中其实主要做了 @Import({NacosConfigBeanDefinitionRegistrar.class})。所以只要关注两个Registar即可,NacosConfigBeanDefinitionRegistrar 以及 NacosConfigBootBeanDefinitionRegistrar。
NacosConfigBeanDefinitionRegistrar
registerBeanDefinitions() 注册方法是特别核心的一个注册方法,包含 Nacos 启动以及后续处理等。
registerGlobalNacosProperties()
注册了一个名为 globalNacosProperties$config的Properties 对象,持有 Nacos的 全局配置信息。
registerNacosCommonBeans()
这里主要注册了一些 Nacos 通用的 Bean 对象。
registerNacosApplicationContextHolder()
这个很简单,实现了 ApplicationContextAware 对象,用于持有 ApplicationContext。
registerAnnotationNacosInjectedBeanPostProcessor()
注册了 AnnotationNacosInjectedBeanPostProcessor,这个我没大看明白。首先初始化了AbstractNacosServiceBeanBuilder 相关的一个 Builder Map。然后如果使用 @NacosInjected 注解注入的类可以通过NacosServiceBeanBuilder 进行生成注入,而不是通过Spring的容器进行统一管理。
@Override
protected Object doGetInjectedBean(NacosInjected annotation, Object bean,
String beanName, Class<?> injectedType,
InjectionMetadata.InjectedElement injectedElement) {
AbstractNacosServiceBeanBuilder serviceBeanBuilder = nacosServiceBeanBuilderMap
.get(injectedType);
return serviceBeanBuilder.build(annotation.properties());
}
registerNacosConfigBeans()
这个方法里面注入了 Nacos Config 相关的一些核心类,用于实现配置的读取、解析、配置变化的回调更新等。
registerPropertySourcesPlaceholderConfigurer()
注册 PropertySourcesPlaceholderConfigurer,这个也是 Spring 中最常用的占位符解析器了,主要就是占位符的解析、填充。
registerNacosConfigPropertiesBindingPostProcessor()
注册了 NacosConfigurationPropertiesBindingPostProcessor,看名字也可以知道这个类实现了 BeanPostProcesso r接口,主要看 postProcessBeforeInitialization() 方法。
首先判断是否为 @NacosConfigurationProperties 注解标记的对象,对 @NacosConfigurationProperties 标记的对象,执行 bind() 方法,绑定到 NacosConfigurationPropertiesBinder 对象上,这个对象在 SpringBoot 下是NacosBootConfigurationPropertiesBinder 而在常规 Spring 项目下为 NacosConfigurationPropertiesBinder,因为NacosBootConfigurationPropertiesBinder 使用了 SpringBoot 特有的 Binder 进行属性绑定。
protected void bind(final Object bean, final String beanName, final NacosConfigurationProperties properties) {
......
final ConfigService configService = configServiceBeanBuilder
.build(properties.properties());
// Add a Listener if auto-refreshed
if (properties.autoRefreshed()) {
Listener listener = new AbstractListener() {
@Override
public void receiveConfigInfo(String config) {
doBind(bean, beanName, dataId, groupId, type, properties, config,
configService);
}
};
try {//
if (configService instanceof EventPublishingConfigService) {
((EventPublishingConfigService) configService).addListener(dataId,
groupId, type, listener);
}
else {
configService.addListener(dataId, groupId, listener);
}
}
catch (NacosException e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage(), e);
}
}
}
String content = getContent(configService, dataId, groupId);
if (hasText(content)) {
doBind(bean, beanName, dataId, groupId, type, properties, content,
configService);
}
}
在 bind() 方中,当配置为 autoRefresh 时候,会新建一个 Listener 并添加到 ConfigService 中,通知会先调用一次 doBind() 方法来首先获取一次配置信息。当在监听到变化的时候同样会回调 doBind() 方法。
doBind() 方法是在常规 Spring 下使用 DataBinder 进行数据绑定,而在 SpringBoot 下使用 Binder 进行数据绑定。
// 常规Spring下的doBind()方法
protected void doBind(Object bean, String beanName, String dataId, String groupId,
String type, NacosConfigurationProperties properties, String content,
ConfigService configService) {
final String prefix = properties.prefix();
PropertyValues propertyValues = NacosUtils.resolvePropertyValues(bean, prefix,
dataId, groupId, content, type);
doBind(bean, properties, propertyValues);
publishBoundEvent(bean, beanName, dataId, groupId, properties, content,
configService);
publishMetadataEvent(bean, beanName, dataId, groupId, properties);
}
private void doBind(Object bean, NacosConfigurationProperties properties,
PropertyValues propertyValues) {
ObjectUtils.cleanMapOrCollectionField(bean);
DataBinder dataBinder = new DataBinder(bean);
dataBinder.setAutoGrowNestedPaths(properties.ignoreNestedProperties());
dataBinder.setIgnoreInvalidFields(properties.ignoreInvalidFields());
dataBinder.setIgnoreUnknownFields(properties.ignoreUnknownFields());
dataBinder.bind(propertyValues);
}
// SpringBoot下的doBind()方法
protected void doBind(Object bean, String beanName, String dataId, String groupId,
String configType, NacosConfigurationProperties properties, String content,
ConfigService configService) {
String name = "nacos-bootstrap-" + beanName;
NacosPropertySource propertySource = new NacosPropertySource(name, dataId,
groupId, content, configType);
environment.getPropertySources().addLast(propertySource);
Binder binder = Binder.get(environment);
ResolvableType type = getBeanType(bean, beanName);
Bindable<?> target = Bindable.of(type).withExistingValue(bean);
binder.bind(properties.prefix(), target);
publishBoundEvent(bean, beanName, dataId, groupId, properties, content,
configService);
publishMetadataEvent(bean, beanName, dataId, groupId, properties);
environment.getPropertySources().remove(name);
}
registerNacosConfigListenerMethodProcessor()
注册了 NacosConfigListenerMethodProcessor 对象,继承自 AnnotationListenerMethodProcessor 。AnnotationListenerMethodProcessor 类实现了 ApplicationListener ,用以监听 ContextRefreshedEvent 事件。
在 onApplicationEvent() 中,会为每个带有 @NacosConfigListener 注解的方法,执行 processListenerMethod() 方法。
protected void processListenerMethod(String beanName, final Object bean,
Class<?> beanClass, final NacosConfigListener listener, final Method method,
ApplicationContext applicationContext) {
... ...
ConfigService configService = configServiceBeanBuilder
.build(listener.properties());
try {
configService.addListener(dataId, groupId,
new TimeoutNacosConfigListener(dataId, groupId, timeout) {
@Override
protected void onReceived(String config) {
Class<?> targetType = method.getParameterTypes()[0];
NacosConfigConverter configConverter = determineNacosConfigConverter(
targetType, listener, type);
Object parameterValue = configConverter.convert(config);
// Execute target method
ReflectionUtils.invokeMethod(method, bean, parameterValue);
}
});
}
catch (NacosException e) {
logger.error("ConfigService can't add Listener for dataId : " + dataId
+ " , groupId : " + groupId, e);
}
publishMetadataEvent(beanName, bean, beanClass, dataId, groupId, listener,
method);
}
processListenerMethod() 方法中会增加一个监听事件,configService.addListener,当配置发生变化时候进行回调,使用反射调用当前 @NacosConfigListener 注解的方法,参数为最新的配置信息。
registerNacosPropertySourcePostProcessor()
注册 NacosPropertySourcePostProcessor,用于处理 @NacosPropertySource 注解的类。
private void processPropertySource(String beanName,
ConfigurableListableBeanFactory beanFactory) {
... ...
// Build multiple instance if possible
List<NacosPropertySource> nacosPropertySources = buildNacosPropertySources(
beanName, beanDefinition);
// Add Orderly
for (NacosPropertySource nacosPropertySource : nacosPropertySources) {
addNacosPropertySource(nacosPropertySource);
Properties properties = configServiceBeanBuilder
.resolveProperties(nacosPropertySource.getAttributesMetadata());
addListenerIfAutoRefreshed(nacosPropertySource, properties, environment);
}
processedBeanNames.add(beanName);
}
NacosPropertySourcePostProcessor 实现了 BeanFactoryPostProcessor 接口,在 postProcessBeanFactory() --> processPropertySource() 方法中会使用 AbstractNacosPropertySourceBuilder 判断符合条件的 BeanDefinition 对象,构建并加载 NacosPropertySource 对象,对符合提交件的 Bean 执行配置的属性解析替换。
public static void addListenerIfAutoRefreshed(final NacosPropertySource nacosPropertySource, final Properties properties,
final ConfigurableEnvironment environment) {
... ...
ConfigService configService = nacosServiceFactory
.createConfigService(properties);
Listener listener = new AbstractListener() {
@Override
public void receiveConfigInfo(String config) {
String name = nacosPropertySource.getName();
NacosPropertySource newNacosPropertySource = new NacosPropertySource(
dataId, groupId, name, config, type);
newNacosPropertySource.copy(nacosPropertySource);
MutablePropertySources propertySources = environment
.getPropertySources();
// replace NacosPropertySource
propertySources.replace(name, newNacosPropertySource);
}
};
if (configService instanceof EventPublishingConfigService) {
((EventPublishingConfigService) configService).addListener(dataId,
groupId, type, listener);
}
else {
configService.addListener(dataId, groupId, listener);
}
}
addListenerIfAutoRefreshed() 其实跟讲过的添加监听过程类似,都是新建一个 Listener 然后加入到 ConfigService 中,当有收到变化通知时候执行相应的回调函数。
registerAnnotationNacosPropertySourceBuilder()
注册 AnnotationNacosPropertySourceBuilder 继承自 AbstractNacosPropertySourceBuilder,也是上面执行过程多次用到过的类, 解析 @NacosPropertySource 注解配置,生成、初始化 NacosPropertySource 对象等。
protected void initNacosPropertySource(NacosPropertySource nacosPropertySource,
AnnotatedBeanDefinition beanDefinition,
Map<String, Object> annotationAttributes) {
// AttributesMetadata
initAttributesMetadata(nacosPropertySource, annotationAttributes);
// Auto-Refreshed
initAutoRefreshed(nacosPropertySource, annotationAttributes);
// Origin
initOrigin(nacosPropertySource, beanDefinition);
// Order
initOrder(nacosPropertySource, annotationAttributes);
}
registerNacosConfigListenerExecutor()
注册一个名为 nacosConfigListenerExecutor 的无界线程池,线程名为 NacosConfigListener-ThreadPool-N。
registerNacosValueAnnotationBeanPostProcessor()
注册 NacosValueAnnotationBeanPostProcessor 继承自 AnnotationInjectedBeanPostProcessor,由于是 BeanPostProcessor 看入口方法 postProcessBeforeInitialization()。
public Object postProcessBeforeInitialization(Object bean, final String beanName)
throws BeansException {
doWithFields(bean, beanName);
doWithMethods(bean, beanName);
return super.postProcessBeforeInitialization(bean, beanName);
}
private void doWithFields(final Object bean, final String beanName) {
ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException {
NacosValue annotation = getAnnotation(field, NacosValue.class);
doWithAnnotation(beanName, bean, annotation, field.getModifiers(), null, field);
}
});
}
private void doWithAnnotation(String beanName, Object bean, NacosValue annotation, int modifiers, Method method,
Field field) {
if (annotation != null) {
if (Modifier.isStatic(modifiers)) {
return;
}
if (annotation.autoRefreshed()) {
String placeholder = resolvePlaceholder(annotation.value());
if (placeholder == null) {
return;
}
NacosValueTarget nacosValueTarget = new NacosValueTarget(bean, beanName, method, field);
put2ListMap(placeholderNacosValueTargetMap, placeholder, nacosValueTarget);
}
}
}
postProcessBeforeInitialization 方法中将会扫描 @NacosValue 注解标注的 Field 及 Method ,将其组织成为一个 Map<String,List<NacosValueTarget>> 形式。Map key 为 @NacosValue 注解的value值,Map value 为 NacosValueTarget 对象,是 @NacosValue 注解值对应的 Bean 及 Field 或者 Method 对象的封装,因为可能有多个所以为 List。
private static class NacosValueTarget {
private final Object bean;
private final String beanName;
private final Method method;
private final Field field;
private String lastMD5;
}
这里需要注意当多个配置文件中有相同的 key 是不会区分的,如果有相同的 key 只能用前缀区分。
NacosValueAnnotationBeanPostProcessor 实现了 BeanPostProcessor 的同时,也实现了 ApplicationListener 接口,所以这个类也会处理 NacosConfigReceivedEvent 事件。
public void onApplicationEvent(NacosConfigReceivedEvent event) {
for (Map.Entry<String, List<NacosValueTarget>> entry : placeholderNacosValueTargetMap.entrySet()) {
String key = environment.resolvePlaceholders(entry.getKey());
String newValue = environment.getProperty(key);
if (newValue == null) {
continue;
}
List<NacosValueTarget> beanPropertyList = entry.getValue();
for (NacosValueTarget target : beanPropertyList) {
String md5String = MD5.getInstance().getMD5String(newValue);
boolean isUpdate = !target.lastMD5.equals(md5String);
if (isUpdate) {
target.updateLastMD5(md5String);
if (target.method == null) {
setField(target, newValue);
} else {
setMethod(target, newValue);
}
}
}
}
}
当接收到事件变化的时候,onApplicationEvent 中根据变化的 key,从上面组织的 Map 中找到需要变更的字段对象,使用反射进行值更新。
registerConfigServiceBeanBuilder()
注册 ConfigServiceBeanBuilder,用于生成 ConfigService 对象。ConfigService 是 Nacos Client 对外暴露的核心 API 接口,用于获取配置,添加监听等。
registerLoggingNacosConfigMetadataEventListener()
注册 LoggingNacosConfigMetadataEventListener,用于启动时候输出元数据信息等。
invokeNacosPropertySourcePostProcessor()
这里立即调用改了下 NacosPropertySourcePostProcessor#postProcessBeanFactory(),是为了提高@NacosPropertySource 处理的优先级。
NacosConfigBootBeanDefinitionRegistrar
registerBeanDefinitions()
注册了 NacosBootConfigurationPropertiesBinder 类,用于 Spring Boot 中使用 Binder 来更新对象属性,这个也是上文中说到的 Spring Boot 下与普通 Spring 项目不同的地方。
可以看到 Nacos Config Spring Boot 工具中使用了大量的 BeanPostProcessor、BeanFactoryPostProcessor、ApplicationListener 等 Spring 的扩展点来进行原有 Bean 的增强、处理,并且通过事件、反射等机制很好的实现了远程属性的获取,及属性变化的及时更新。同时又一次证明了 Spring 真的是一个扩展性很好的框架,很值得我们深入学习。
网友评论