程序员

Nacos Config Spring Boot

2022-05-23  本文已影响0人  鱼蛮子9527

这里通过 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 真的是一个扩展性很好的框架,很值得我们深入学习。

上一篇下一篇

猜你喜欢

热点阅读