美文网首页程序员
Nacos Config Spring Boot

Nacos Config Spring Boot

作者: 鱼蛮子9527 | 来源:发表于2022-05-23 09:48 被阅读0次

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

    相关文章

      网友评论

        本文标题:Nacos Config Spring Boot

        本文链接:https://www.haomeiwen.com/subject/bucrprtx.html