Nacos之Spring源码分析

909 阅读32分钟

一、前言

Nacos全面拥抱Spring生态,提供了快速构建Spring应用的nacos-spring-context项目,该项目提供了以下功能:

  • 注解驱动
  • 依赖注入
  • 外部化配置
  • 事件驱动

本章将详细介绍其源码实现,分析的Nacos Spring项目版本为1.1.1版本,对应的Nacos版本为1.4.1

二、服务启动

2.1 基于注解启动

@EnableNacos注解可以启用Nacos Spring的所有功能,包括服务发现以及分布式配置。该注解与@EnableNacosDiscovery@EnableNacosConfig两个起到相同的作用,前者用于开启服务发现,后者用于开启分布式配置。

我们来看下@EnableNacos的定义:

@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(NacosBeanDefinitionRegistrar.class)
public @interface EnableNacos {

	/**
	 * Global {@link NacosProperties Nacos Properties}
	 *
	 * @return required
	 * @see NacosInjected#properties()
	 * @see NacosConfigListener#properties()
	 * @see NacosConfigurationProperties#properties()
	 */
	NacosProperties globalProperties();
}

可以看到@EnableNacos注解通过NacosBeanDefinitionRegistrar类来具体实现,再看下其继承关系及核心逻辑:

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                    BeanDefinitionRegistry registry) {
    BeanDefinition annotationProcessor = BeanDefinitionBuilder
        .genericBeanDefinition(PropertySourcesPlaceholderConfigurer.class)
        .getBeanDefinition();
    registry.registerBeanDefinition(
        PropertySourcesPlaceholderConfigurer.class.getName(),
        annotationProcessor);

    AnnotationAttributes attributes = AnnotationAttributes
        .fromMap(importingClassMetadata
                 .getAnnotationAttributes(EnableNacos.class.getName()));

    // Register Global Nacos Properties Bean
    registerGlobalNacosProperties(attributes, registry, environment,
                                  GLOBAL_NACOS_PROPERTIES_BEAN_NAME);
    // Register Nacos Annotation Beans
    registerNacosAnnotationBeans(registry);
    // Invoke NacosPropertySourcePostProcessor immediately
    // in order to enhance the precedence of @NacosPropertySource process
    invokeNacosPropertySourcePostProcessor(beanFactory);
}

public void registerNacosAnnotationBeans(BeanDefinitionRegistry registry) {
    registerNacosCommonBeans(registry);
    registerNacosConfigBeans(registry, environment, beanFactory);
    registerNacosDiscoveryBeans(registry);
}

registerBeanDefinitions方法中,主要做了4件事:

  1. 注册PropertySourcesPlaceholderConfigurer BeanDefinition,后面用于生成相关Bean。
  2. 读取@EnableNacos注解内的属性,作为全局属性Bean注册到Spring容器中。
  3. 注册被Nacos相关注解修饰相关类的Bean Definition,后面用于生成相关Bean,此外,实现依赖自动注入,外部化配置以及事件驱动。
  4. 创建NacosPropertySourcePostProcessor后置处理器,开始@NacosPropertySource注解处理。

下面会对这几块逐一进行详细分析。

2.2 基于XML启动

2.2.1 XSD定义

为了启用XML功能,首先需要定义一个XSD文件。在nacos-spring-context项目中,XSD定义在资源目录的nacos.xsd文件中,如下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns="http://nacos.io/schema/nacos"
            targetNamespace="http://nacos.io/schema/nacos">
  
  <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
  
  <xsd:annotation>
    <xsd:documentation>
      <![CDATA[ Nacos Framework Schema ]]>
    </xsd:documentation>
  </xsd:annotation>
  
  <xsd:element name="annotation-driven">
    <xsd:annotation>
      <xsd:documentation>
        <![CDATA[
             Activates various Nacos Spring's annotations :
             @NacosInjected
             @NacosConfigListener
             @NacosPropertySource
             @NacosPropertySources
             @NacosConfigProperties / @NacosProperty / @NacosIgnore
   ]]></xsd:documentation>
    </xsd:annotation>
  </xsd:element>
  
  <xsd:element name="global-properties">
    <xsd:annotation>
      <xsd:documentation>
        <![CDATA[
                Register Nacos global Properties whose values are configured from attributes.
                ]]>
      </xsd:documentation>
    </xsd:annotation>
    <xsd:complexType>
      <xsd:attribute name="endpoint" default="${nacos.endpoint:}"/>
      <xsd:attribute name="namespace" default="${nacos.endpoint:}"/>
      <xsd:attribute name="access-key" default="${nacos.access-key:}"/>
      <xsd:attribute name="secret-key" default="${nacos.secret-key:}"/>
      <xsd:attribute name="server-addr" default="${nacos.server-addr:}"/>
      <xsd:attribute name="context-path" default="${nacos.context-path:}"/>
      <xsd:attribute name="cluster-name" default="${nacos.cluster-name:}"/>
      <xsd:attribute name="encode" default="${nacos.encode:UTF-8}"/>
      <xsd:attribute name="username" default="${nacos.username:}"/>
      <xsd:attribute name="password" default="${nacos.password:}"/>
    </xsd:complexType>
  </xsd:element>
  
  <xsd:element name="property-source">
    <xsd:annotation>
      <xsd:documentation>
        <![CDATA[
                Add Nacos property source
                ]]>
      </xsd:documentation>
    </xsd:annotation>
    <xsd:complexType>
      <xsd:attribute name="name" default=""/>
      <xsd:attribute name="group-id" default="DEFAULT_GROUP"/>
      <xsd:attribute name="data-id" use="required"/>
      <xsd:attribute name="auto-refreshed" default="false"/>
      <xsd:attribute name="first" default="false"/>
      <xsd:attribute name="before" default=""/>
      <xsd:attribute name="after" default=""/>
      <xsd:attribute name="type" default="properties"/>
    </xsd:complexType>
  </xsd:element>
  
</xsd:schema>

在这个文件中有3个核心的元素声明:

  • annotation-driven:启用注解功能。一旦在我们的XML文件中使用了该标签,也就意味着我们的项目将会支持@NacosInjected@NacosConfigListener@NacosPropertySource@NacosPropertySources@NacosConfigProperties@NacosProperty和@NacosIgnore
  • global-properties:注册全局属性,与@EnableNacos注解中的globalProperties属性一样。
  • property-source:注册Property Source,与@NacosPropertySource实现同样的功能。

2.2.2 NamespaceHandler以及BeanDefinitionParser定义

NamespaceHandler用于定义XML中元素的处理类,并由BeanDefinitionParser实现具体的处理。在nacos-spring-context项目中,NacosNamespaceHandler类继承了NamespaceHandler类,并分别注册了NacosAnnotationDrivenBeanDefinitionParserGlobalNacosPropertiesBeanDefinitionParserNacosPropertySourceBeanDefinitionParser,分别实现上面介绍的3个核心元素的处理。

public class NacosNamespaceHandler extends NamespaceHandlerSupport {
    
    @Override
    public void init() {
        registerBeanDefinitionParser("annotation-driven",
                                     new NacosAnnotationDrivenBeanDefinitionParser());
        registerBeanDefinitionParser("global-properties",
                                     new GlobalNacosPropertiesBeanDefinitionParser());
        registerBeanDefinitionParser("property-source",
                                     new NacosPropertySourceBeanDefinitionParser());
    }
}

2.2.2.1 NacosAnnotationDrivenBeanDefinitionParser

NacosAnnotationDrivenBeanDefinitionParser用于开启注解支持,实现被注解修饰的Bean可以被自动扫描。该类实现了BeanDefinitionParser接口中的parse方法,会创建一个NacosBeanDefinitionRegistrar调用registerNacosAnnotationBeans方法实现Nacos相关注解的处理,该方面与前面基于注解启动部分介绍的registerNacosAnnotationBeans是同一个方法,不再多介绍。因为该标签不会被其他其他标签引用,这里直接返回了null。

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {

    // Get Environment
    Environment environment = parserContext.getDelegate().getReaderContext()
        .getReader().getEnvironment();
    // Get BeanDefinitionRegistry
    BeanDefinitionRegistry registry = parserContext.getRegistry();
    // Register Nacos Annotation Beans
    NacosBeanDefinitionRegistrar registrar = new NacosBeanDefinitionRegistrar();
    registrar.setEnvironment(environment);
    registrar.registerNacosAnnotationBeans(registry);
    return null;
}

2.2.2.2 GlobalNacosPropertiesBeanDefinitionParser

GlobalNacosPropertiesBeanDefinitionParser用于Nacos全局属性的处理,实现将XML中的nacos:global-properties元素转换成全局属性。该类只实现了BeanDefinitionParser接口中的parse方法,实现上也很简单,就是从Element元素中读取各项属性,并调用registerGlobalNacosProperties方法将全局属性注册到Spring容器中,该Bean为单例模式,如果Spring容器已存在就不会注册。因为该Bean不会被作为内嵌对象使用,所以这里直接返回了null。

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {

    Properties properties = new Properties();

    Environment environment = parserContext.getDelegate().getReaderContext()
        .getReader().getEnvironment();

    properties.setProperty(PropertyKeyConst.ENDPOINT, element.getAttribute(ENDPOINT));
    properties.setProperty(PropertyKeyConst.NAMESPACE,
                           element.getAttribute(NAMESPACE));
    properties.setProperty(PropertyKeyConst.ACCESS_KEY,
                           element.getAttribute(ACCESS_KEY));
    properties.setProperty(PropertyKeyConst.SECRET_KEY,
                           element.getAttribute(SECRET_KEY));
    properties.setProperty(PropertyKeyConst.SERVER_ADDR,
                           element.getAttribute(SERVER_ADDR));
    properties.setProperty(PropertyKeyConst.CLUSTER_NAME,
                           element.getAttribute(CLUSTER_NAME));
    properties.setProperty(PropertyKeyConst.ENCODE, element.getAttribute(ENCODE));
    properties.setProperty(PropertyKeyConst.USERNAME, element.getAttribute(USERNAME));
    properties.setProperty(PropertyKeyConst.PASSWORD, element.getAttribute(PASSWORD));

    BeanDefinitionRegistry registry = parserContext.getRegistry();

    // Register Global Nacos Properties as Spring singleton bean
    registerGlobalNacosProperties(properties, registry, environment,
                                  GLOBAL_NACOS_PROPERTIES_BEAN_NAME);

    return null;
}

2.2.2.3 NacosPropertySourceBeanDefinitionParser

NacosPropertySourceBeanDefinitionParser用于基于XML的Property Source处理,这个类有一个核心的处理方法parseInternal,实现将XML中的Property Source元素解析成Spring的Bean Definition。在实现上首先会调用registerNacosPropertySourcePostProcessorregisterXmlNacosPropertySourceBuilder方法,向容器中注入所依赖的Bean,即NacosPropertySourcePostProcessor以及XmlNacosPropertySourceBuilder。其中,NacosPropertySourcePostProcessor在Spring所有Bean Definition加载完毕之后实例化之前实现拦截,读取所有的Property Source的统一处理。XmlNacosPropertySourceBuilder则专门用于基于XML的Property Source处理,用于XML的Element元素读取各项配置。这两个Bean在下文介绍registerAnnotationNacosPropertySourceBuilder方法时会详细介绍,这里就不再多介绍。接着就会将XML中的Element元数据包装成NacosPropertySourceXmlBeanDefinition并返回,在Property Source统一处理时由XmlNacosPropertySourceBuilder实现处理。

@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
    BeanDefinitionRegistry registry = parserContext.getRegistry();
    // Register Dependent Beans
    registerNacosPropertySourcePostProcessor(registry);
    registerXmlNacosPropertySourceBuilder(registry);

    NacosPropertySourceXmlBeanDefinition beanDefinition = new NacosPropertySourceXmlBeanDefinition();
    beanDefinition.setElement(element);
    beanDefinition.setXmlReaderContext(parserContext.getReaderContext());

    return beanDefinition;
}

2.2.3 spring.handlersspring.schmas配置

spring.handlersspring.schmas是为了Spring在解析XML时可以感知到Nacos自定义的元素,并调用相应的Parser实现相应的处理。

  • spring.handlers内容如下,声明了NamespaceHandler的实现类。
http://nacos.io/schema/nacos=com.alibaba.nacos.spring.context.config.xml.NacosNamespaceHandler
  • spring.schmas内容如下,声明了Nacos的XSD定义文件。
http://nacos.io/schema/nacos.xsd=META-INF/schemas/nacos.xsd

三、registerBeanDefinitions实现

3.1 PropertySources注册

这一步很简单,向容器中注入PropertySourcesPlaceholderConfigurerBean Definition,用后续生成Bean对象,实现占位符${...}的解析以及@Value注解的处理。

BeanDefinition annotationProcessor = BeanDefinitionBuilder.genericBeanDefinition(PropertySourcesPlaceholderConfigurer.class).getBeanDefinition();
registry.registerBeanDefinition(PropertySourcesPlaceholderConfigurer.class.getName(), annotationProcessor);

3.2 全局属性Bean注入

获取@EnableNacos注解中的globalProperties属性,调用registerGlobalNacosProperties方法,创建一个名为globalNacosPropertiesProperties对象,作为全局性的Nacos属性配置,该Bean为单例模式,在注册时会先判断Spring容器中是否已存在,不存在时才会注册。

// Register Global Nacos Properties Bean
registerGlobalNacosProperties(attributes, registry, environment,
                              GLOBAL_NACOS_PROPERTIES_BEAN_NAME);

当前,在创建Bean之前,会基于Environment的具体实现类实现对属性配置中的占位符、表达式计算替换。

public static void registerGlobalNacosProperties(AnnotationAttributes attributes, BeanDefinitionRegistry registry, PropertyResolver propertyResolver, String beanName) {
    if (attributes == null) {
        return; // Compatible with null
    }
    AnnotationAttributes globalPropertiesAttributes = attributes
        .getAnnotation("globalProperties");
    registerGlobalNacosProperties((Map<?, ?>) globalPropertiesAttributes, registry, propertyResolver, beanName);
}

public static void registerGlobalNacosProperties(Map<?, ?> globalPropertiesAttributes, BeanDefinitionRegistry registry, PropertyResolver propertyResolver, String beanName) {
    Properties globalProperties = resolveProperties(globalPropertiesAttributes,  propertyResolver);
    registerSingleton(registry, beanName, globalProperties);
}

3.3 Nacos相关注解处理

这一步是最为核心的一步,正如其方法registerNacosAnnotationBeans实现逻辑一样,实现3个模块的Bean Definition注册,用于后续启动Spring容器时生成对应的Bean,分别是:

  • Nacos公共Bean Definition注册。
  • Nacos配置中心相关Bean Definition注册。
  • Nacos注册中心相关Bean Definition注册。
public void registerNacosAnnotationBeans(BeanDefinitionRegistry registry) {
    registerNacosCommonBeans(registry);
    registerNacosConfigBeans(registry, environment, beanFactory);
    registerNacosDiscoveryBeans(registry);
}

3.3.1 Nacos公共Bean Definition注册

public static void registerNacosCommonBeans(BeanDefinitionRegistry registry) {
    // Register NacosApplicationContextHolder Bean
    registerNacosApplicationContextHolder(registry);
    // Register AnnotationNacosInjectedBeanPostProcessor Bean
    registerAnnotationNacosInjectedBeanPostProcessor(registry);
}

公共Bean Definition主要包括两个,一个是ApplicationContextHolderBean,用于维护ApplicationContext容器对象。另一个是AnnotationNacosInjectedBeanPostProcessor,用于实现@NacosInjected修饰的成员变量的自动注入,而其注入的对象恰好就是ConfigService以及NamingService,分别实现了配置中心和注册中心客户端的功能。要分析AnnotationNacosInjectedBeanPostProcessor,必须先分析其父类AbstractAnnotationBeanPostProcessor

AbstractAnnotationBeanPostProcessor与Spring框架中的AutowiredAnnotationBeanPostProcessor类类似,用于实现指定注解修饰的属性的自动注入。除了AnnotationNacosInjectedBeanPostProcessor,它还有另外一个实现类NacosValueAnnotationBeanPostProcessor,用于@NacosValue注解的自动注入。

AbstractAnnotationBeanPostProcessor类只有一个构造方法,其子类在创建实例对象时,需要传入一个要关注的注解类型数组,从而实现这些注解类型数组修饰的成员变量、成员方法的自动注入。

public AbstractAnnotationBeanPostProcessor(Class<? extends Annotation>... annotationTypes) {
    Assert.notEmpty(annotationTypes, "The argument of annotations' types must not empty");
    this.annotationTypes = annotationTypes;
}

相应地,AnnotationNacosInjectedBeanPostProcessor类也只有一个构造方法,用于调用父类设置要关注的注解@NacosInjected,实现@NacosInjected修饰的成员变量、成员方法的自动注入。

public AnnotationNacosInjectedBeanPostProcessor() {
    super(NacosInjected.class);
}

AbstractAnnotationBeanPostProcessor类的成员方法中,有一个postProcessMergedBeanDefinition方法,为MergedBeanDefinitionPostProcessor类中的抽象方法,一般用于Spring框架内部,在实例化Bean时,缓存一些元数据,比如获取类中被特定注解修饰的成员变量、成员方法等。在AnnotationNacosInjectedBeanPostProcessor类的继承体系中,用于缓存@NacosInjected修饰的成员变量和成员方法。

@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    if (beanType != null) {
        InjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
        metadata.checkConfigMembers(beanDefinition);
    }
}

private InjectionMetadata findInjectionMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {
    // Fall back to class name as cache key, for backwards compatibility with custom callers.
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    // Quick check on the concurrent map first, with minimal locking.
    AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                try {
                    metadata = buildAnnotatedMetadata(clazz);
                    this.injectionMetadataCache.put(cacheKey, metadata);
                } catch (NoClassDefFoundError err) {
                    throw new IllegalStateException("Failed to introspect object class [" + clazz.getName() +
                                                    "] for annotation metadata: could not find class that it depends on", err);
                }
            }
        }
    }
    return metadata;
}


public void checkConfigMembers(RootBeanDefinition beanDefinition) {
    Set<InjectedElement> checkedElements = new LinkedHashSet<InjectedElement>(this.injectedElements.size());
    for (InjectedElement element : this.injectedElements) {
        Member member = element.getMember();
        if (!beanDefinition.isExternallyManagedConfigMember(member)) {
            beanDefinition.registerExternallyManagedConfigMember(member);
            checkedElements.add(element);
            if (logger.isDebugEnabled()) {
                logger.debug("Registered injected element on class [" + this.targetClass.getName() + "]: " + element);
            }
        }
    }
    this.checkedElements = checkedElements;
}

在具体实现上,首先会调用findInjectionMetadata,该方法首先会计算缓存Key,如果beanName不为空,则使用beanName,否则使用Bean类的全限定名。计算出缓存Key时,调用this.injectionMetadataCache.get(cacheKey)获取元数据,当然第一次获取时并不存在,会调用buildAnnotatedMetadata(clazz)方法来构建元数据,并调用this.injectionMetadataCache.put(cacheKey, metadata)存入缓存中。

private AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class<?> beanClass) {
    Collection<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> fieldElements = findFieldAnnotationMetadata(beanClass);
    Collection<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> methodElements = findAnnotatedMethodMetadata(beanClass);
    return new AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);
}

private List<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> findFieldAnnotationMetadata(final Class<?> beanClass) {

    final List<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> elements = new LinkedList<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement>();

    ReflectionUtils.doWithFields(beanClass, new ReflectionUtils.FieldCallback() {
        @Override
        public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {

            for (Class<? extends Annotation> annotationType : getAnnotationTypes()) {

                AnnotationAttributes attributes = doGetAnnotationAttributes(field, annotationType);

                if (attributes != null) {

                    if (Modifier.isStatic(field.getModifiers())) {
                        if (logger.isWarnEnabled()) {
                            logger.warn("@" + annotationType.getName() + " is not supported on static fields: " + field);
                        }
                        return;
                    }

                    elements.add(new AnnotatedFieldElement(field, attributes));
                }
            }
        }
    });

    return elements;

}

private List<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> findAnnotatedMethodMetadata(final Class<?> beanClass) {

    final List<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> elements = new LinkedList<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement>();

    ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
        @Override
        public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {

            Method bridgedMethod = findBridgedMethod(method);

            if (!isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }


            for (Class<? extends Annotation> annotationType : getAnnotationTypes()) {

                AnnotationAttributes attributes = doGetAnnotationAttributes(bridgedMethod, annotationType);

                if (attributes != null && method.equals(ClassUtils.getMostSpecificMethod(method, beanClass))) {
                    if (Modifier.isStatic(method.getModifiers())) {
                        if (logger.isWarnEnabled()) {
                            logger.warn("@" + annotationType.getName() + " annotation is not supported on static methods: " + method);
                        }
                        return;
                    }
                    if (method.getParameterTypes().length == 0) {
                        if (logger.isWarnEnabled()) {
                            logger.warn("@" + annotationType.getName() + " annotation should only be used on methods with parameters: " +
                                        method);
                        }
                    }
                    PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, beanClass);
                    elements.add(new AnnotatedMethodElement(method, pd, attributes));
                }
            }
        }
    });

    return elements;
}

buildAnnotatedMetadata方法会分别调用findFieldAnnotationMetadata(beanClass)findAnnotatedMethodMetadata(beanClass)方法,分别获取@NacosInjected修饰的成员变量以及成员方法以及注解属性,封装成InjectionMetadata.InjectedElement元数据(成员变量使用AnnotatedFieldElement,成员方法使用AnnotatedMethodElement),最终将获取到的成员变量,成员方法封装成AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements)对象返回。

在调用完findInjectionMetadata获取元数据之后,会调用metadata.checkConfigMembers(beanDefinition),将上面收集到成员变量、成员方法放入到该Bean Definition的externallyManagedConfigMembers成员变量中,完成要注入属性的最终收集。

AbstractAnnotationBeanPostProcessor类的成员方法中,另一个关键方法就是postProcessPropertyValues,为InstantiationAwareBeanPostProcessor类中的抽象方法,同样用于Spring框架底层,用于实现@NacosInjected修饰的成员变量以及成员方法的自动注入。

@Override
public PropertyValues postProcessPropertyValues(
    PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {

    InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
    try {
        metadata.inject(bean, beanName, pvs);
    } catch (BeanCreationException ex) {
        throw ex;
    } catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getSimpleName()
                                        + " dependencies is failed", ex);
    }
    return pvs;
}

public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable {
    Collection<InjectedElement> elementsToIterate =
        (this.checkedElements != null ? this.checkedElements : this.injectedElements);
    if (!elementsToIterate.isEmpty()) {
        boolean debug = logger.isDebugEnabled();
        for (InjectedElement element : elementsToIterate) {
            if (debug) {
                logger.debug("Processing injected element of bean '" + beanName + "': " + element);
            }
            element.inject(target, beanName, pvs);
        }
    }
}

在具体实现上,同样会先调用findInjectionMetadata方法,获取之前收集的元数据(@NacosInjected修饰的成员变量和成员方法),并调用metadata.inject(bean, beanName, pvs)方法实现自动注入。再看inject方法的具体实现,会对之前收集到的成员变量以及成员方法进行遍历,调用element.inject(target, beanName, pvs来实现注入。成员变量和成员方法分别有不同的具体实现,都是通过反射实现对象的自动注入。

public class AnnotatedFieldElement extends InjectionMetadata.InjectedElement {

    ...

    @Override
    protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {

        Class<?> injectedType = resolveInjectedType(bean, field);

        Object injectedObject = getInjectedObject(attributes, bean, beanName, injectedType, this);

        ReflectionUtils.makeAccessible(field);

        field.set(bean, injectedObject);

    }

    ...
}

private class AnnotatedMethodElement extends InjectionMetadata.InjectedElement {

    ...

    @Override
    protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {

        Class<?> injectedType = pd.getPropertyType();

        Object injectedObject = getInjectedObject(attributes, bean, beanName, injectedType, this);

        ReflectionUtils.makeAccessible(method);

        method.invoke(bean, injectedObject);

    }

}

对于要注入对象,则是调用了getInjectedObject(attributes, bean, beanName, injectedType, this)方法来获取。其在内部实现上,首先是调用了buildInjectedObjectCacheKey方法构建被缓存对象的Key,并从被注入对象缓存中获取相应对象,如果获取到则返回,否则会调用doGetInjectedBean(attributes, bean, beanName, injectedType, injectedElement)来获取被注入对象,并放入到缓存中。当然,buildInjectedObjectCacheKeydoGetInjectedBeanAbstractAnnotationBeanPostProcessor类中是两个抽象方法,其具体实现在其子类中。

protected Object getInjectedObject(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                   InjectionMetadata.InjectedElement injectedElement) throws Exception {

    String cacheKey = buildInjectedObjectCacheKey(attributes, bean, beanName, injectedType, injectedElement);

    Object injectedObject = injectedObjectsCache.get(cacheKey);

    if (injectedObject == null) {
        injectedObject = doGetInjectedBean(attributes, bean, beanName, injectedType, injectedElement);
        // Customized inject-object if necessary
        injectedObjectsCache.putIfAbsent(cacheKey, injectedObject);
    }

    return injectedObject;

}

我们来看下AnnotationNacosInjectedBeanPostProcessor类对buildInjectedObjectCacheKeydoGetInjectedBean的具体实现。

protected String buildInjectedObjectCacheKey(AnnotationAttributes attributes,
                                             Object bean, String beanName, Class<?> injectedType,
                                             InjectionMetadata.InjectedElement injectedElement) {
    StringBuilder keyBuilder = new StringBuilder(injectedType.getSimpleName());

    AbstractNacosServiceBeanBuilder serviceBeanBuilder = nacosServiceBeanBuilderMap
        .get(injectedType);

    if (serviceBeanBuilder == null) {
        throw new UnsupportedOperationException(format(
            "Only support to inject types[%s] instance , however actual injected type [%s] in member[%s]",
            nacosServiceBeanBuilderMap.keySet(), injectedType,
            injectedElement.getMember()));
    }

    Map<String, Object> nacosProperties = getNacosProperties(attributes);

    Properties properties = serviceBeanBuilder.resolveProperties(nacosProperties);

    keyBuilder.append(properties);

    return keyBuilder.toString();
}

public final Properties resolveProperties(
    Map<String, Object> nacosPropertiesAttributes) {
    Properties globalNacosProperties = resolveGlobalNacosProperties();
    return NacosUtils.resolveProperties(nacosPropertiesAttributes, environment,
                                        globalNacosProperties);
}

protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean,
                                   String beanName, Class<?> injectedType,
                                   InjectionMetadata.InjectedElement injectedElement) throws Exception {
    AbstractNacosServiceBeanBuilder serviceBeanBuilder = nacosServiceBeanBuilderMap
        .get(injectedType);

    Map<String, Object> nacosProperties = getNacosProperties(attributes);

    return serviceBeanBuilder.build(nacosProperties);
}

public S build(Map<String, Object> nacosPropertiesAttributes) {

    NacosServiceFactory nacosServiceFactory = getNacosServiceFactoryBean(beanFactory);
    Properties properties = resolveProperties(nacosPropertiesAttributes);

    if (properties.isEmpty()) {
        throw new BeanCreationException(
            format("The @%s attributes must be configured",
                   NacosProperties.class.getSimpleName()));
    }

    try {
        return createService(nacosServiceFactory, properties);
    }
    catch (NacosException e) {
        throw new BeanCreationException(e.getErrMsg(), e);
    }
}

buildInjectedObjectCacheKey中会根据具体要注入的对象从成员变量nacosServiceBeanBuilderMap.get(injectedType)中获取对应的AbstractNacosServiceBeanBuilder。接着会基于@NacosInjected注解内部的属性值attributes,通过调用resolveProperties方法解析出具体的属性值,构建出最终的缓存Key。resolveProperties方法在实现上,会先获取之前生成全局Nacos属性配置,结合@NacosInjected属性值,合并成最终的属性值,在合并时,@NacosInjected的属性值优先级更高。此外,也说明了另一个问题,如果@NacosInjected修饰了多个成员变量,且其属性值相同或者最终合并出来的属性值相同,则注入的对象将会是同一个。接下来我们看下doGetInjectedBean方法的实现,该方法同样会调用getNacosProperties方法获取@NacosInjected属性值,并调用AbstractNacosServiceBeanBuilderbuild方法来创建要注入的对象。build方法在具体实现上,同样会调用resolveProperties(nacosPropertiesAttributes)方法合并全局Nacos属性以及@NacosInjected属性值,并根据不同的AbstractNacosServiceBeanBuilder实现类的createService来创建要注入的对象。

介绍到这里,基本就介绍完了AnnotationNacosInjectedBeanPostProcessor类的具体用途以及实现,但上面一直在反复提到AbstractNacosServiceBeanBuilder类,这个后面会有具体介绍,我们先来简单介绍下,有个印象。该抽象类有3个具体的实现的类,分别为用于配置中心的ConfigServiceBeanBuilder,用于创建配置中心的实例对象ConfigService。用于注册中心的NamingServiceBeanBuilder,用于创建注册中心的实例对象NamingService。用于注册中心维护的NamingMaintainServiceBeanBuilder,用于创建注册中心维护的实例对象NamingMaintainService

3.3.2 Nacos配置中心相关Bean注册

public static void registerNacosConfigBeans(BeanDefinitionRegistry registry, Environment environment, BeanFactory beanFactory) {
    // Register PropertySourcesPlaceholderConfigurer Bean
    registerPropertySourcesPlaceholderConfigurer(registry, beanFactory);

    registerNacosConfigPropertiesBindingPostProcessor(registry);

    registerNacosConfigListenerMethodProcessor(registry);

    registerNacosPropertySourcePostProcessor(registry);

    registerAnnotationNacosPropertySourceBuilder(registry);

    registerNacosConfigListenerExecutor(registry, environment);

    registerNacosValueAnnotationBeanPostProcessor(registry);

    registerConfigServiceBeanBuilder(registry);

    registerLoggingNacosConfigMetadataEventListener(registry);
}

registerNacosConfigBeans实现了Nacos配置中心相关的Bean注册,下面一一来介绍一下。

3.3.2.1 registerPropertySourcesPlaceholderConfigurer

这一步用于向容器中注入PropertySourcesPlaceholderConfigurerBean Definition,实现后续的配置管理。在前面章节已经介绍过,那为何又在此处重复注册呢?原因在于用户可能只使用了配置中心功能,不会触发前面章节的注册功能。

3.3.2.2 registerNacosConfigPropertiesBindingPostProcessor

向容器中注册NacosConfigurationPropertiesBindingPostProcessorBean,为使用@NacosConfigurationProperties注解修饰的类创建一个ConfigService,实现配置的自动注入。

NacosConfigurationPropertiesBindingPostProcessor的继承关系如上,在Bean实例属性注入完成之后实例被初始化之前会先调用postProcessBeforeInitialization方法,判断该类是否被@NacosConfigurationProperties注解修饰,如果有被修饰,表明需要生成ConfigService实例并调用bind方法自动注入。

public Object postProcessBeforeInitialization(Object bean, String beanName)
    throws BeansException {

    NacosConfigurationProperties nacosConfigurationProperties = findAnnotation(
        bean.getClass(), NacosConfigurationProperties.class);

    if (nacosConfigurationProperties != null) {
        bind(bean, beanName, nacosConfigurationProperties);
    }

    return bean;
}

protected void bind(final Object bean, final String beanName,
                    final NacosConfigurationProperties properties) {

    Assert.notNull(bean, "Bean must not be null!");

    Assert.notNull(properties, "NacosConfigurationProperties must not be null!");

    // support read data-id and group-id from spring environment
    final String dataId = NacosUtils.readFromEnvironment(properties.dataId(),
                                                         environment);
    final String groupId = NacosUtils.readFromEnvironment(properties.groupId(),
                                                          environment);
    final String type;

    ConfigType typeEunm = properties.yaml() ? ConfigType.YAML : properties.type();
    if (ConfigType.UNSET.equals(typeEunm)) {
        type = NacosUtils.readFileExtension(dataId);
    }
    else {
        type = typeEunm.getType();
    }

    final ConfigService configService = configServiceBeanBuilder
        .build(properties.properties());

    // Add a Listener if auto-refreshed
    if (properties.autoRefreshed()) {

        String content = getContent(configService, dataId, groupId);

        if (hasText(content)) {
            doBind(bean, beanName, dataId, groupId, type, properties, content,
                   configService);
        }

        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);
            }
        }
    }
}

bind方法一层一层调用下去,会看到configServiceBeanBuilder.build(properties.properties())方法调用来创建ConfigService实例对象,这在前面章节也提到过,我们来看下具体实现。

public class ConfigServiceBeanBuilder
		extends AbstractNacosServiceBeanBuilder<ConfigService> {

	/**
	 * The bean name of {@link ConfigServiceBeanBuilder}
	 */
	public static final String BEAN_NAME = "configServiceBeanBuilder";

	protected ConfigServiceBeanBuilder() {
		super(GlobalNacosPropertiesSource.CONFIG);
	}

	@Override
	protected ConfigService createService(NacosServiceFactory nacosServiceFactory,
			Properties properties) throws NacosException {
		return nacosServiceFactory.createConfigService(properties);
	}
}

ConfigServiceBeanBuilder类继承了AbstractNacosServiceBeanBuilder,并实现了createService方法,其内部通过nacosServiceFactory.createConfigService(properties)调用来创建ConfigService实现类的对象,最终实现上是调用了ConfigCreateWorker类的run方法。

public ConfigService createConfigService(Properties properties) throws NacosException {
  Properties copy = new Properties();
  copy.putAll(properties);
  return (ConfigService) createWorkerManager.get(ServiceType.CONFIG).run(copy, null);
}

class ConfigCreateWorker extends AbstractCreateWorker<ConfigService> {

    ConfigCreateWorker() {
    }

    @Override
    public ConfigService run(Properties properties, ConfigService service)
        throws NacosException {
        String cacheKey = identify(properties);
        ConfigService configService = configServicesCache.get(cacheKey);

        if (configService == null) {
            if (service == null) {
                service = NacosFactory.createConfigService(properties);
            }
            configService = new EventPublishingConfigService(service, properties,
                                                             getSingleton().context,
                                                             getSingleton().nacosConfigListenerExecutor);
            configServicesCache.put(cacheKey, configService);
        }
        return configService;
    }
}

首先,会调用identify方法基于properties参数计算出唯一标识,这样相同的属性配置即使在多处使用,也只会有一个ConfigService,避免重复创建,唯一标识就通过namespaceserverAddresscontextPathclusterNameendpointaccessKeysecretKeyencode来唯一确定。在计算出缓存Key后,会从namingServicesCache缓存中获取对象,如果获取不到,则调用NacosFactory.createConfigService(properties)创建,创建好的ConfigService是Nacos客户端原生的Service,在真正使用时对其进行包装,包装成EventPublishingConfigService对象,放入缓存中并返回。EventPublishingConfigService重写了ConfigService所有接口,并做了下面功能的增强:

  1. 对监听器Listener进行代理包装成DelegatingEventPublishingListener对象,实现配置变更后的自动刷新。
  2. 增加监听器注册、配置发布、配置移除、监听器移除等事件发布,用于日志记录。
  3. 增加获取ConfigService元数据的getProperties()方法。
  4. 增加实例销毁时自动清理的destroy()方法。
public class EventPublishingConfigService
		implements ConfigService, NacosServiceMetaData, DisposableBean {

	private final ConfigService configService;

	private final ApplicationEventPublisher applicationEventPublisher;

	private final Executor executor;

	private final Properties properties;

	public EventPublishingConfigService(ConfigService configService,
			Properties properties, ConfigurableApplicationContext context,
			Executor executor) {
		this.configService = configService;
		this.properties = properties;
		this.applicationEventPublisher = new DeferredApplicationEventPublisher(context);
		this.executor = executor;
	}

	@Override
	public String getConfig(String dataId, String group, long timeoutMs)
			throws NacosException {
		try {
			return configService.getConfig(dataId, group, timeoutMs);
		}
		catch (NacosException e) {
			if (NacosException.SERVER_ERROR == e.getErrCode()) { // timeout error
				publishEvent(new NacosConfigTimeoutEvent(configService, dataId, group,
						timeoutMs, e.getErrMsg()));
			}
			throw e; // re-throw NacosException
		}
	}

	@Override
	public String getConfigAndSignListener(String dataId, String group, long timeoutMs,
			Listener listener) throws NacosException {
		Listener listenerAdapter = new DelegatingEventPublishingListener(configService,
				dataId, group, applicationEventPublisher, executor, listener);
		return configService.getConfigAndSignListener(dataId, group, timeoutMs,
				listenerAdapter);
	}

	/**
	 * Implementation of the new version of support for multiple configuration file type
	 * resolution.
	 *
	 * @param dataId dataId
	 * @param group group
	 * @param type config's type
	 * @param listener listener
	 * @throws NacosException NacosException
	 */
	public void addListener(String dataId, String group, String type, Listener listener)
			throws NacosException {
		Listener listenerAdapter = new DelegatingEventPublishingListener(configService,
				dataId, group, type, applicationEventPublisher, executor, listener);
		addListener(dataId, group, listenerAdapter);
	}

	@Override
	public void addListener(String dataId, String group, Listener listener)
			throws NacosException {
		configService.addListener(dataId, group, listener);
		publishEvent(new NacosConfigListenerRegisteredEvent(configService, dataId, group,
				listener, true));
	}

	@Override
	public boolean publishConfig(String dataId, String group, String content)
			throws NacosException {
		boolean published = configService.publishConfig(dataId, group, content);
		publishEvent(new NacosConfigPublishedEvent(configService, dataId, group, content,
				published));
		return published;
	}
	
	@Override
	public boolean publishConfig(String dataId, String group, String content, String type) throws NacosException {
		boolean published = configService.publishConfig(dataId, group, content, type);
		publishEvent(new NacosConfigPublishedEvent(configService, dataId, group, content,
				published));
		return published;
	}
	
	@Override
	public boolean removeConfig(String dataId, String group) throws NacosException {
		boolean removed = configService.removeConfig(dataId, group);
		publishEvent(new NacosConfigRemovedEvent(configService, dataId, group, removed));
		return removed;
	}

	@Override
	public void removeListener(String dataId, String group, Listener listener) {
		configService.removeListener(dataId, group, listener);
		publishEvent(new NacosConfigListenerRegisteredEvent(configService, dataId, group,
				listener, false));
	}

	@Override
	public String getServerStatus() {
		return configService.getServerStatus();
	}

	@Override
	public void shutDown() throws NacosException {
		configService.shutDown();
	}

	private void publishEvent(NacosConfigEvent nacosConfigEvent) {
		applicationEventPublisher.publishEvent(nacosConfigEvent);
	}

	@Override
	public Properties getProperties() {
		return properties;
	}

	/**
	 * Destroy lifecycle method to invoke {@link #shutDown()}
	 * @throws Exception
	 * @since 1.0.0
	 */
	@Override
	public void destroy() throws Exception {
		shutDown();
	}
}

实现上都很简单,这里不再多说。对于配置变更的事件通知及自动刷新机制,会在registerNacosPropertySourcePostProcessor以及registerNacosValueAnnotationBeanPostProcessor做详细介绍。

3.3.2.3 registerNacosConfigListenerMethodProcessor(有个疑问,配置首次怎么设置上去的?难道添加了监听之后就会触发调用么?)

这一步向容器中注入NacosConfigListenerMethodProcessorBean,用于@NacosConfigListener注解的处理,实现被@NacosConfigListener修饰的配置的自动刷新。

NacosConfigListenerMethodProcessor的继承关系如上,首先要介绍下其父类AnnotationListenerMethodProcessor,抽象类AnnotationListenerMethodProcessor实现了ApplicationListener<ContextRefreshedEvent>接口,用于Spring容器ContextRefreshedEvent事件的监听,实现了被@NacosConfigListener注解修饰的方法的处理。我们都知道,ContextRefreshedEvent事件在ApplicationPreparedEvent事件之后,ApplicationStartedEvent事件之前触发,即在BeanDefinition已被加载刷新之后,但在ApplicationRunnerCommandLineRunner调用之前触发。也就是说,在Spring容器刷新之后,来处理被@NacosConfigListener注解所修饰的Bean,实现相关配置的自动注入及监听。

在具体实现上,AnnotationListenerMethodProcessor重写了onApplicationEvent方法,获取触发ContextRefreshedEvent事件对应的容器,从容器中获取所有Bean,并对这些Bean进行遍历处理。通过Class<?> beanClass = AopUtils.getTargetClass(bean)方法调用,可以获取该bean对应的beanClass,获取到Class之后,就可以使用前面介绍过的ReflectionUtils.doWithMethods方法,获取该Class中所有的成员方法,从中找符合监听条件(通过isListenerMethod来判断,方法必须被public修饰、非静态、非Native、非抽象以及返回类为void)且是候选的方法(通过isCandidateMethod判断,由子类实现),接下来就是调用processListenerMethod(beanName, bean, beanClass, annotation, method, applicationContext)方法实现最后的处理,processListenerMethod方法同样由其子类来实现。

@Override
public final void onApplicationEvent(ContextRefreshedEvent event) {
    // Retrieve ApplicationContext from ContextRefreshedEvent
    ApplicationContext applicationContext = event.getApplicationContext();
    // Select those methods from all beans that annotated
    processBeans(applicationContext);
}

private void processBeans(ApplicationContext applicationContext) {

    Map<String, Object> beansMap = applicationContext.getBeansOfType(Object.class,
                                                                     false, false);

    processBeans(beansMap, applicationContext);
}

private void processBeans(Map<String, Object> beansMap,
                          ApplicationContext applicationContext) {

    for (Map.Entry<String, Object> entry : beansMap.entrySet()) {
        String beanName = entry.getKey();
        Object bean = entry.getValue();
        // Bean type
        if (bean != null) {
            Class<?> beanClass = AopUtils.getTargetClass(bean);
            processBean(beanName, bean, beanClass, applicationContext);
        }
    }

}

private void processBean(final String beanName, final Object bean,
                         final Class<?> beanClass, final ApplicationContext applicationContext) {

    ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
        @Override
        public void doWith(Method method)
            throws IllegalArgumentException, IllegalAccessException {
            A annotation = AnnotationUtils.getAnnotation(method, annotationType);
            if (annotation != null && isCandidateMethod(bean, beanClass, annotation,
                                                        method, applicationContext)) {
                processListenerMethod(beanName, bean, beanClass, annotation, method,
                                      applicationContext);
            }
        }

    }, new ReflectionUtils.MethodFilter() {
        @Override
        public boolean matches(Method method) {
            return isListenerMethod(method);
        }
    });

}

protected abstract void processListenerMethod(String beanName, Object bean,
                                              Class<?> beanClass, A annotation, Method method,
                                              ApplicationContext applicationContext);

介绍完了父类AnnotationListenerMethodProcessor,我们来看下NacosConfigListenerMethodProcessor类对于isCandidateMethodprocessListenerMethod方法的实现。

isCandidateMethod方法,该方法用于判断是否为需要实现配置自动注入的候选方法。在实现上,首先判断该方法的入参是不是只有1个,在多个入参时不支持配置的自动注入。接着,会调用determineNacosConfigConverter获取类型转换器,如果用户在@NacosConfigListener中指定了自定类转换器,则会根据配置的Class创建出一个转换器对象。否则,会使用默认的类型转换器DefaultNacosConfigConverter。在获取到类型转换器之后,会调用configConverter.canConvert(targetType)来判断目标类型是否可以转换,其内部实现也很简单,使用了Spring接口ConversionService中的canConvert方法,判断String类型是否可以转换成目标类型,这里不再多说。

@Override
protected boolean isCandidateMethod(Object bean, Class<?> beanClass,
                                    NacosConfigListener listener, Method method,
                                    ApplicationContext applicationContext) {
    Class<?>[] parameterTypes = method.getParameterTypes();

    if (parameterTypes.length != 1) { // Only one argument on method
        if (logger.isWarnEnabled()) {
            logger.warn("Listener method [" + method
                        + "] parameters' count must be one !");
        }
        return false;
    }

    Class<?> targetType = parameterTypes[0];

    NacosConfigConverter configConverter = determineNacosConfigConverter(targetType,
                                                                         listener, listener.type().getType());

    if (!configConverter.canConvert(targetType)) {
        if (logger.isWarnEnabled()) {
            logger.warn("Listener method [" + method
                        + "] is not a candidate , thus its parameter type [" + targetType
                        + "] can't be converted , please check NacosConfigConverter implementation : "
                        + configConverter.getClass().getName());
        }
    }

    return true;
}

processListenerMethod方法,该方法实现了配置的自动注入以及配置变更监听。方法前面的处理逻辑很简单,都是调用了configServiceBeanBuilder.build(listener.properties())来获取ConfigService对象(如果不存在则创建),前面都有介绍。下一步就是向ConfigService中注册监听器,在Nacos服务端修改配置时,客户端会调用注册的回掉方法onReceived实现配置的自动更新,在实现上,首先会根据目标类型确认转换器,转换器会将传进来新的配置值config进行转换调用Object parameterValue = configConverter.convert(config),得到目标值parameterValue,再通过反射方式实现配置的更新。

@Override
protected void processListenerMethod(String beanName, final Object bean,
                                     Class<?> beanClass, final NacosConfigListener listener, final Method method,
                                     ApplicationContext applicationContext) {

    final String dataId = NacosUtils.readFromEnvironment(listener.dataId(),
                                                         environment);
    final String groupId = NacosUtils.readFromEnvironment(listener.groupId(),
                                                          environment);
    final String type;

    ConfigType typeEunm = listener.type();
    if (ConfigType.UNSET.equals(typeEunm)) {
        type = NacosUtils.readFileExtension(dataId);
    }
    else {
        type = typeEunm.getType();
    }

    long timeout = listener.timeout();

    Assert.isTrue(StringUtils.hasText(dataId), "dataId must have content");
    Assert.isTrue(StringUtils.hasText(groupId), "groupId must have content");
    Assert.isTrue(timeout > 0, "timeout must be greater than zero");

    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);

}

当然,TimeoutNacosConfigListener监听器正如其名字一样,为超时配置监听器。服务端配置发生变更时,首先会调用receiveConfigInfo方法,receiveConfigInfo在回调onReceived方法时,基于一个线程池采用异步方式,如果回调超时(默认1秒)都没响应时,会取消调用。因此,我们在具体实现onReceived时,需要保证快速响应。

public abstract class TimeoutNacosConfigListener extends AbstractListener {

	private static ExecutorService executorService = Executors.newScheduledThreadPool(8,
			new ThreadFactory() {
				@Override
				public Thread newThread(Runnable r) {
					Thread t = new Thread(r);
					t.setDaemon(true);
					t.setName("com.alibaba.nacos.spring.configListener-"
							+ id.incrementAndGet());
					return t;
				}
			});

	@Override
	public void receiveConfigInfo(final String content) {
		Future future = executorService.submit(new Runnable() {
			@Override
			public void run() {
				onReceived(content);
			}
		});
		try {
			future.get(timeout, TimeUnit.MILLISECONDS);
		}
		catch (InterruptedException e) {
			Thread.currentThread().interrupt();
			throw new RuntimeException(e);
		}
		catch (ExecutionException e) {
			throw new RuntimeException(e);
		}
		catch (TimeoutException e) {
			future.cancel(true);
			logger.warn(
					"Listening on Nacos Config exceeds timeout {} ms "
							+ "[dataId : {}, groupId : {}, data : {}]",
					timeout, dataId, groupId, content);
		}
	}

	/**
	 * process Nacos Config when received.
	 *
	 * @param content Nacos Config
	 */
	protected abstract void onReceived(String content);

	/**
	 * Get timeout in milliseconds
	 *
	 * @return timeout in milliseconds
	 */
	public long getTimeout() {
		return timeout;
	}
}

在添加到完回调方法后,会执行最后一步操作publishMetadataEvent(beanName, bean, beanClass, dataId, groupId, listener, method),推送元数据更新事件NacosConfigMetadataEvent,用于配置变更的日志记录,这个后面章节会有介绍。

3.3.2.4 registerNacosPropertySourcePostProcessor

这一步向容器中注入NacosPropertySourcePostProcessorBean,用于@NacosPropertySource的处理。在NacosPropertySourcePostProcessor的实现方法中,最主要就是postProcessBeanFactory方法,该方法在所有Bean Definition被加载之后,但任何Bean都还未被实例化之前调用。在实现上,首先会从Spring容器中获取AbstractNacosPropertySourceBuilder的所有子类(AnnotationNacosPropertySourceBuilderXmlNacosPropertySourceBuilder),存入到nacosPropertySourceBuilders集合中。其中,XmlNacosPropertySourceBuilder在前面已经提到过,用于基于XML的Property Source处理,AnnotationNacosPropertySourceBuilder则是用于基于注解的Property Source处理,在registerAnnotationNacosPropertySourceBuilder方法中会介绍。接着,会调用getConfigServiceBeanBuilder(beanFactory)方法,从Spring容器中获取配置中心Service的Builder。最后就是从Spring容器中获取所有注册的BeanName,调用processPropertySource(beanName, beanFactory)方法实现Property Source的处理。

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
    throws BeansException {
    String[] abstractNacosPropertySourceBuilderBeanNames = BeanUtils
        .getBeanNames(beanFactory, AbstractNacosPropertySourceBuilder.class);

    this.nacosPropertySourceBuilders = new ArrayList<AbstractNacosPropertySourceBuilder>(
        abstractNacosPropertySourceBuilderBeanNames.length);

    for (String beanName : abstractNacosPropertySourceBuilderBeanNames) {
        this.nacosPropertySourceBuilders.add(beanFactory.getBean(beanName,
                                                                 AbstractNacosPropertySourceBuilder.class));
    }

    NacosPropertySourcePostProcessor.beanFactory = beanFactory;
    this.configServiceBeanBuilder = getConfigServiceBeanBuilder(beanFactory);

    String[] beanNames = beanFactory.getBeanDefinitionNames();

    for (String beanName : beanNames) {
        processPropertySource(beanName, beanFactory);
    }
}

processPropertySource方法首先会判断该BeanName是否已处理过,如果处理过则直接返回。否则,会从Spring容器中获取该Bean的定义。接下来就是关键处理逻辑,调用List<NacosPropertySource> nacosPropertySources = buildNacosPropertySources(beanName, beanDefinition)计算出该Bean上所有的Property Source。因为有多个Property Source,这就涉及到相同属性的覆盖顺序,此时会调用addNacosPropertySource方法根据Property Source在定义时的顺序有序地添加到environment(维护了所有属性配置,为接口ConfigurableEnvironment子类的对象,在Web环境下子类为StandardServletEnvironment,非Web环境下为StandardEnvironment)中,而后就是调用addListenerIfAutoRefreshed(nacosPropertySource, properties, environment)方法添加Listener,实现配置变更时的自动刷新。处理完成后会将该BeanName添加到processedBeanNames集合中,标识该BeanName已被处理。

private void processPropertySource(String beanName,
                                   ConfigurableListableBeanFactory beanFactory) {

    if (processedBeanNames.contains(beanName)) {
        return;
    }

    BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);

    // 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);
}

介绍完上面的处理流程,我们来看下buildNacosPropertySources方法的具体实现。BeanDefinition可能是通过@NacosPropertySource方式生成,也可能是通过XML标签中的nacos:property-source生成,因此会遍历前面得到的nacosPropertySourceBuilders列表,通过supports方法判断builder支持该BeanDefinition的处理,判断方法很简单,这里不再多说。

private List<NacosPropertySource> buildNacosPropertySources(String beanName,
                                                            BeanDefinition beanDefinition) {
    for (AbstractNacosPropertySourceBuilder builder : nacosPropertySourceBuilders) {
        if (builder.supports(beanDefinition)) {
            return builder.build(beanName, beanDefinition);
        }
    }
    return Collections.emptyList();
}

确认了builder之后,会调用build方法将BeanDefinition对象转换成NacosPropertySource对象。实现上,首先会调用resolveRuntimeAttributesArray方法,提取Property Source属性配置attributesArray,注解和XML有不同实现,后面会有介绍。如果获取到的attributesArray为空,则直接返回,否则会对该Map进行遍历,调用doBuild方法,来真正地创建NacosPropertySource。接着就会调用createMetaEvent方法来创建一个元数据变更事件,并调用publishMetadataEvent方法向Spring容器发布事件,用于日志记录。最后就是将得到的nacosPropertySource加入到列表中返回。

public List<NacosPropertySource> build(String beanName, T beanDefinition) {

    Map<String, Object>[] attributesArray = resolveRuntimeAttributesArray(
        beanDefinition, globalNacosProperties);

    int size = attributesArray == null ? 0 : attributesArray.length;

    if (size == 0) {
        return Collections.emptyList();
    }

    List<NacosPropertySource> nacosPropertySources = new ArrayList<NacosPropertySource>(
        size);

    for (int i = 0; i < size; i++) {
        Map<String, Object> attributes = attributesArray[i];
        if (!CollectionUtils.isEmpty(attributes)) {

            NacosPropertySource nacosPropertySource = doBuild(beanName,
                                                              beanDefinition, attributesArray[i]);

            NacosConfigMetadataEvent metadataEvent = createMetaEvent(
                nacosPropertySource, beanDefinition);

            initMetadataEvent(nacosPropertySource, beanDefinition, metadataEvent);

            publishMetadataEvent(metadataEvent);

            nacosPropertySources.add(nacosPropertySource);

        }
    }

    return nacosPropertySources;
}

介绍完build方法的处理流程,我们再来看下build方法中跳过的resolveRuntimeAttributesArraydoBuild方法。resolveRuntimeAttributesArray方法,该方法用于从BeanDefinition中提取Property Source相关的属性,在AbstractNacosPropertySourceBuilder类中为抽象方法,由子类AnnotationNacosPropertySourceBuilder以及XmlNacosPropertySourceBuilder具体实现。实现上也很简单,可以看下源码。其中,AnnotationNacosPropertySourceBuilder会从BeanDefinition中读取注解的元数据,解析出Property Source相关的属性。而XmlNacosPropertySourceBuilder则是从BeanDefinition读取前面在“NacosPropertySourceBeanDefinitionParser”一节提到的元素Element,提取Element中属性。我们再来看下doBuild方法,首先会从上面读取的到属性中提取name、dataId和groupId,并基于环境变量对其中占位符、表达式进行替换。接着会从属性中读取配置类型,如果未设置则基于dataId的后缀来判断,后缀也为空时则默认使用properties类型。下面就调用runtimeAttributes.get(PROPERTIES_ATTRIBUTE_NAME)读取连接Nacos Server相关的配置信息properties(对应@NacosPropertySource#properties()),调用resolveProperties方法将其中的占位符、表达式进行替换,并和Nacos全局属性进行合并,当然,如果有相同属性时,全局属性的优先级更低。下面就可以调用nacosConfigLoader.load(dataId, groupId, nacosProperties)读取datId及groupId对应的配置值nacosConfig,并封装出最终的NacosPropertySource对象,在调用initNacosPropertySource后返回,initNacosPropertySource只是做属性元数据、自动刷新、配置来源以及NacosPropertySource顺序的初始化工作,比较简单,感兴趣的可以自己阅读下源码。

protected NacosPropertySource doBuild(String beanName, T beanDefinition,
                                      Map<String, Object> runtimeAttributes) {

    // Get annotation metadata
    String name = (String) runtimeAttributes.get(NAME_ATTRIBUTE_NAME);
    String dataId = (String) runtimeAttributes.get(DATA_ID_ATTRIBUTE_NAME);
    String groupId = (String) runtimeAttributes.get(GROUP_ID_ATTRIBUTE_NAME);

    dataId = NacosUtils.readFromEnvironment(dataId, environment);
    groupId = NacosUtils.readFromEnvironment(groupId, environment);

    final String type;

    ConfigType typeEunm = (ConfigType) runtimeAttributes.get(CONFIG_TYPE_ATTRIBUTE_NAME);
    if (ConfigType.UNSET.equals(typeEunm)) {
        type = NacosUtils.readFileExtension(dataId);
    }
    else {
        type = typeEunm.getType();
    }

    Map<String, Object> nacosPropertiesAttributes = (Map<String, Object>) runtimeAttributes
        .get(PROPERTIES_ATTRIBUTE_NAME);

    Properties nacosProperties = resolveProperties(nacosPropertiesAttributes,
                                                   environment, globalNacosProperties);

    String nacosConfig = nacosConfigLoader.load(dataId, groupId, nacosProperties);

    if (!StringUtils.hasText(nacosConfig)) {
        if (logger.isWarnEnabled()) {
            logger.warn(format(
                "There is no content for NacosPropertySource from dataId[%s] , groupId[%s] , properties[%s].",
                dataId, groupId, nacosPropertiesAttributes));
        }
    }

    if (!StringUtils.hasText(name)) {
        name = buildDefaultPropertySourceName(dataId, groupId, nacosProperties);
    }

    NacosPropertySource nacosPropertySource = new NacosPropertySource(dataId, groupId,
                                                                      name, nacosConfig, type);

    nacosPropertySource.setBeanName(beanName);

    String beanClassName = beanDefinition.getBeanClassName();
    if (StringUtils.hasText(beanClassName)) {
        nacosPropertySource.setBeanType(resolveClassName(beanClassName, classLoader));
    }
    nacosPropertySource.setGroupId(groupId);
    nacosPropertySource.setDataId(dataId);
    nacosPropertySource.setProperties(nacosProperties);

    initNacosPropertySource(nacosPropertySource, beanDefinition, runtimeAttributes);

    return nacosPropertySource;

}

至此,buildNacosPropertySources方法介绍完了。我们再来看下addNacosPropertySource,该方法用于向Spring的环境Environment注入前面生成的NacosPropertySource,这样配置中心相关的配置都可以被当前Spring应用所使用,比如@Value,也可以注入Nacos的配置,只是自动刷新。在实现上,首先会从Environment接口中读取当前Spring应用已维护PropertySource列表对象propertySources。接着会根据当前nacosPropertySource所定义的顺序确定插入的位置,如果指定了第1位,则插入到propertySources列表中的第1位。否则判断是否指定了相对位置,当然,这里的相对主要相对的是systemEnvironment以及systemProperties。如果未配置顺序,则默认插入到列表中最后一个位置。

private void addNacosPropertySource(NacosPropertySource nacosPropertySource) {

    MutablePropertySources propertySources = environment.getPropertySources();

    boolean first = nacosPropertySource.isFirst();
    String before = nacosPropertySource.getBefore();
    String after = nacosPropertySource.getAfter();

    boolean hasBefore = !nullSafeEquals(DEFAULT_STRING_ATTRIBUTE_VALUE, before);
    boolean hasAfter = !nullSafeEquals(DEFAULT_STRING_ATTRIBUTE_VALUE, after);

    boolean isRelative = hasBefore || hasAfter;

    if (first) { // If First
        propertySources.addFirst(nacosPropertySource);
    }
    else if (isRelative) { // If relative
        if (hasBefore) {
            propertySources.addBefore(before, nacosPropertySource);
        }
        if (hasAfter) {
            propertySources.addAfter(after, nacosPropertySource);
        }
    }
    else {
        propertySources.addLast(nacosPropertySource); // default add last
    }
}

接下里就是前面跳过的最后一个方法addListenerIfAutoRefreshed,顾名思义,就是为配置增加监听器,感知到配置变更的事件,并做后置的处理。如果该nacosPropertySource未配置自动刷新,则直接返回。否则,读取nacosPropertySource中的dataId、groupId以及type,并从Spring当前环境属性中读取到Nacos Server端的配置调用nacosServiceFactory.createConfigService(properties)创建配置中心客户端对象configService。接下来就是配置变更的监听器Listener定义,这里会创建一个匿名内部类,在配置发生变更时会触发receiveConfigInfo方法的调用。在处理上,首先会获取原来NacosPropertySource的nme,并基于新的配置值新建一个NacosPropertySource,接下来就是将当前应用中Environment维护的NacosPropertySource基于name用新值替换掉,完成Environment中的配置刷新。最后一步就是调用addListener方法,添加前面创建的Listener,让监听器真正生效。

public static void addListenerIfAutoRefreshed(
    final NacosPropertySource nacosPropertySource, final Properties properties,
    final ConfigurableEnvironment environment) {

    if (!nacosPropertySource.isAutoRefreshed()) { // Disable Auto-Refreshed
        return;
    }

    final String dataId = nacosPropertySource.getDataId();
    final String groupId = nacosPropertySource.getGroupId();
    final String type = nacosPropertySource.getType();
    final NacosServiceFactory nacosServiceFactory = getNacosServiceFactoryBean(
        beanFactory);

    try {

        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);
        }

    }
    catch (NacosException e) {
        throw new RuntimeException(
            "ConfigService can't add Listener with properties : " + properties,
            e);
    }
}

介绍完上面流程,猜测大家可能都会有个疑问,EventPublishingConfigService是做啥用的?配置变更只把Environment中的变更了,但是如果使用了@NacosValue修饰的成员的变量值又是这么自动刷新的?因为nacosServiceFactory.createConfigService(properties)创建出来的都是EventPublishingConfigService类的对象,带着这些疑问,我们来看下EventPublishingConfigService#addListener方法的实现。

public void addListener(String dataId, String group, String type, Listener listener)
    throws NacosException {
    Listener listenerAdapter = new DelegatingEventPublishingListener(configService,
                                                                     dataId, group, type, applicationEventPublisher, executor, listener);
    addListener(dataId, group, listenerAdapter);
}

@Override
public void addListener(String dataId, String group, Listener listener)
    throws NacosException {
    configService.addListener(dataId, group, listener);
    publishEvent(new NacosConfigListenerRegisteredEvent(configService, dataId, group,
                                                        listener, true));
}

首先会对Nacos原生的Listener做一个包装,包装成DelegatingEventPublishingListener,接着就是调用addListener方法,向ConfigService注册,并发布一个事件用于日志记录。前面有提到,在收到配置变更时,会调用监听器的receiveConfigInfo方法,那么看下DelegatingEventPublishingListener#receiveConfigInfo是怎么实现的?在收到配置变更时,首先会调用onReceived方法,最终调用就是delegate.receiveConfigInfo方法,即原生监听器在收到配置变更时调用的处理方法,完成原来逻辑的处理。接着就向容器发布一个配置变更的事件NacosConfigReceivedEvent,这个事件将会由NacosValueAnnotationBeanPostProcessor#onApplicationEvent来处理,从名字就可以猜测出是用于@NacosValue注解的处理,后面会有详细介绍,这里先提一下,留个印象。

@Override
public void receiveConfigInfo(String content) {
    onReceived(content);
    publishEvent(content);
}

private void publishEvent(String content) {
    NacosConfigReceivedEvent event = new NacosConfigReceivedEvent(configService,
                                                                  dataId, groupId, content, configType);
    applicationEventPublisher.publishEvent(event);
}

private void onReceived(String content) {
    delegate.receiveConfigInfo(content);
}

3.3.2.5 registerAnnotationNacosPropertySourceBuilder

这一步用于向Spring容器注册AnnotationNacosPropertySourceBuilder,用于将@NacosPropertySource注解的处理,在前一节已经介绍过,这里就不要多介绍。

3.3.2.6 registerNacosConfigListenerExecutor

这一步用于向容器中注入配置变更监听器所需的执行线程池,在处理上,首先会判断Spring容器中是否已注入了线程池,如果已注入则直接返回。否则,会调用buildNacosConfigListenerExecutor方法来创建固定容量线程池,其中线程池的数量通过调用getParallelism方法获取,用户可以通过nacos.config.listener.parallelism配置来指定,如果未配置则通过调用Runtime.getRuntime().availableProcessors()方法获取机器的CPU核心数来确认。在线程池创建好之后,就会调用registerSingleton(registry, beanName, nacosConfigListenerExecutor)方法向Spring容器注入。

public static void registerNacosConfigListenerExecutor(
    BeanDefinitionRegistry registry, Environment environment) {
    final String beanName = NACOS_CONFIG_LISTENER_EXECUTOR_BEAN_NAME;
    if (registry instanceof BeanFactory
        && ((BeanFactory) registry).containsBean(beanName)) {
        return;
    }
    ExecutorService nacosConfigListenerExecutor = buildNacosConfigListenerExecutor(
        environment);
    registerSingleton(registry, beanName, nacosConfigListenerExecutor);
}

private static ExecutorService buildNacosConfigListenerExecutor(
    Environment environment) {
    int parallelism = getParallelism(environment);
    return Executors.newFixedThreadPool(parallelism, new ThreadFactory() {
        private final AtomicInteger threadNumber = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName("NacosConfigListener-ThreadPool-"
                           + threadNumber.getAndIncrement());
            return thread;
        }
    });
}

private static int getParallelism(Environment environment) {
    int parallelism = environment.getProperty(NACOS_CONFIG_LISTENER_PARALLELISM,
                                              int.class, DEFAULT_NACOS_CONFIG_LISTENER_PARALLELISM);
    return parallelism < 1 ? DEFAULT_NACOS_CONFIG_LISTENER_PARALLELISM : parallelism;
}

3.3.2.7 registerNacosValueAnnotationBeanPostProcessor

这一步向容器中注入NacosValueAnnotationBeanPostProcessorBean,用于@NacosValue注解的处理,实现配置值的自动注入。在前面章节介绍AnnotationNacosInjectedBeanPostProcessor时,已经提过到NacosValueAnnotationBeanPostProcessor,并重点介绍了其父类AbstractAnnotationBeanPostProcessor,这里不再介绍,我们来重点看下NacosValueAnnotationBeanPostProcessor对父类的几个方法的实现。

postProcessBeforeInitialization方法,在Bean被实例化之前被调用,用于记录开启了自动刷新的@NacosValue。该方法会分别调用doWithFieldsdoWithMethods方法,获取被@NacosValue修饰的成员的变量以及成员方法,读取@NacosValue配置。

@Override
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 doWithMethods(final Object bean, final String beanName) {
    ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
        @Override
        public void doWith(Method method) throws IllegalArgumentException {
            NacosValue annotation = getAnnotation(method, NacosValue.class);
            doWithAnnotation(beanName, bean, annotation, method.getModifiers(), method, null);
        }
    });
}

在获取到@NacosValue注解后,会调用doWithAnnotation方法来进行处理。首先,判断是否为静态成员变量或者成员方法,如果是则会直接返回,不支持静态成员变量或者成员方法的注入。接着会判断该@NacosValue属性是否开启了自动刷新annotation.autoRefreshed(),如果不需要直接返回。下面会读取@NacosValue属性中的value,调用resolvePlaceholder提取占位变量,如果未提取到占位变量,表明为表达式或者常量,不支持自动注入。最后会将bean、beanName、method、filed以及@NacosValue属性中的value封装成NacosValueTarget对象,和占位变量一起存到placeholderNacosValueTargetMap,用于后面的自动刷新。

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, annotation.value());
            put2ListMap(placeholderNacosValueTargetMap, placeholder, nacosValueTarget);
        }
    }
}

buildInjectedObjectCacheKey方法,用于计算要注入对象的缓存键。这里使用bean.getClass().getName()以及@NacosValueattributes拼接成唯一键。

@Override
protected String buildInjectedObjectCacheKey(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType, InjectionMetadata.InjectedElement injectedElement) {
    return bean.getClass().getName() + attributes;
}

doGetInjectedBean方法,用于获取@NacosValue属性中的value对应的真正的值。首先,会从注解属性中读取value,调用resolveStringValue实现占位符、表达式的转换。而后根据是成员变量还是成员方法,调用对应的convertIfNecessary方法,转换成目标类型并返回。

@Override
protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType, InjectionMetadata.InjectedElement injectedElement) throws Exception {
    Object value = resolveStringValue(attributes.getString("value"));
    Member member = injectedElement.getMember();
    if (member instanceof Field) {
        return convertIfNecessary((Field) member, value);
    }

    if (member instanceof Method) {
        return convertIfNecessary((Method) member, value);
    }

    return null;
}

private Object resolveStringValue(String strVal) {
    String value = beanFactory.resolveEmbeddedValue(strVal);
    if (exprResolver != null && value != null) {
        return exprResolver.evaluate(value, exprContext);
    }
    return value;
}

private Object convertIfNecessary(Field field, Object value) {
    TypeConverter converter = beanFactory.getTypeConverter();
    return converter.convertIfNecessary(value, field.getType(), field);
}

private Object convertIfNecessary(Method method, Object value) {
    Class<?>[] paramTypes = method.getParameterTypes();
    Object[] arguments = new Object[paramTypes.length];

    TypeConverter converter = beanFactory.getTypeConverter();

    if (arguments.length == 1) {
        return converter.convertIfNecessary(value, paramTypes[0],
                                            new MethodParameter(method, 0));
    }

    for (int i = 0; i < arguments.length; i++) {
        arguments[i] = converter.convertIfNecessary(value, paramTypes[i],
                                                    new MethodParameter(method, i));
    }

    return arguments;
}

onApplicationEvent方法,用于处理NacosConfigReceivedEvent事件,即配置变更的已接收事件,实现@NacosValue的自动刷新功能。前面提到在配置变更被接收时会触发DelegatingEventPublishingListener#receiveConfigInfo方法调用,并向应用发布配置变更事件,事件最终在此处处理。

@Override
public void receiveConfigInfo(String content) {
    onReceived(content);
    publishEvent(content);
}

private void publishEvent(String content) {
    NacosConfigReceivedEvent event = new NacosConfigReceivedEvent(configService,
                                                                  dataId, groupId, content, configType);
    applicationEventPublisher.publishEvent(event);
}

onApplicationEvent在处理时,会遍历之前记录的开启自动刷新的@NacosValue属性缓存placeholderNacosValueTargetMap,获取占位符key,并调用environment.getProperty(key)获取新的配置值newValue。接着就会遍历旧值,判断新值与旧值的md5是否相等,如果不相等则会调用setField或者setMethod方法通过反射方式更新旧值。

@Override
public void onApplicationEvent(NacosConfigReceivedEvent event) {
    // In to this event receiver, the environment has been updated the
    // latest configuration information, pull directly from the environment
    // fix issue #142
    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 = MD5Utils.md5Hex(newValue, "UTF-8");
            boolean isUpdate = !target.lastMD5.equals(md5String);
            if (isUpdate) {
                target.updateLastMD5(md5String);
                Object evaluatedValue = resolveNotifyValue(target.nacosValueExpr, key, newValue);
                if (target.method == null) {
                    setField(target, evaluatedValue);
                }
                else {
                    setMethod(target, evaluatedValue);
                }
            }
        }
    }
}

3.3.2.8 registerConfigServiceBeanBuilder

这一步向容器中注入ConfigServiceBeanBuilderBean,用于创建ConfigService对象,这在前面已介绍过。

3.3.2.9 registerLoggingNacosConfigMetadataEventListener

这一步向容器中注入LoggingNacosConfigMetadataEventListenerBean,用于处理配置变更的元数据事件NacosConfigMetadataEvent。在每次收到变更事件时,会触发onApplicationEvent方法调用,记录变更日志,当然,在记录时会对配置属性中的密码做脱敏处理,避免敏感信息暴露。

public class LoggingNacosConfigMetadataEventListener
		implements ApplicationListener<NacosConfigMetadataEvent> {

	/**
	 * The bean name of {@link LoggingNacosConfigMetadataEventListener}
	 */
	public static final String BEAN_NAME = "loggingNacosConfigMetadataEventListener";
	private final static String LOGGING_MESSAGE = "Nacos Config Metadata : "
			+ "dataId='{}'" + ", groupId='{}'" + ", beanName='{}'" + ", bean='{}'"
			+ ", beanType='{}'" + ", annotatedElement='{}'" + ", xmlResource='{}'"
			+ ", nacosProperties='{}'" + ", nacosPropertiesAttributes='{}'"
			+ ", source='{}'" + ", timestamp='{}'";
	private final Logger logger = LoggerFactory.getLogger(getClass());

	@Override
	public void onApplicationEvent(NacosConfigMetadataEvent event) {
		if (!logger.isInfoEnabled()) { 
			return;
		}
		logger.info(LOGGING_MESSAGE, event.getDataId(), event.getGroupId(),
				event.getBeanName(), event.getBean(), event.getBeanType(),
				event.getAnnotatedElement(), event.getXmlResource(),
				obscuresNacosProperties(event.getNacosProperties()), event.getNacosPropertiesAttributes(),
				event.getSource(), event.getTimestamp());
	}
	
	/**
	 * obscures some private field like password in {@link com.alibaba.nacos.api.annotation.NacosProperties}
	 * @param nacosProperties {@link com.alibaba.nacos.api.annotation.NacosProperties}
	 * @return the properties String after obscures
	 */
	private String obscuresNacosProperties(Map<Object, Object> nacosProperties) {
		String nacosPropertyStr;
		if (nacosProperties != null && nacosProperties.size() > 0) {
			StringBuilder sb = new StringBuilder("{");
			int size = nacosProperties.size();
			int idx = 0;
			for (Map.Entry<Object, Object> e : nacosProperties.entrySet()) {
				Object key = e.getKey();
				Object value = e.getValue();
				sb.append(key);
				sb.append('=');
				// hide some private messages
				if (key != null && NacosProperties.PASSWORD.equals(key.toString())) {
					sb.append("******");
				} else {
					sb.append(value);
				}
				if (idx < size - 1) {
					sb.append(", ");
				}
				idx ++;
			}
			sb.append("}");
			nacosPropertyStr = sb.toString();
		} else {
			nacosPropertyStr = "{}";
		}
		return nacosPropertyStr;
	}
}

3.3.3 Nacos注册中心相关Bean注册

public static void registerNacosDiscoveryBeans(BeanDefinitionRegistry registry) {
    registerNamingServiceBeanBuilder(registry);
    registerNamingMaintainServiceBeanBuilder(registry);
}

registerNacosDiscoveryBeans实现了Nacos注册中心的相关的Bean注册,下面一一介绍下。

3.3.3.1 registerNamingServiceBeanBuilder

registerNamingServiceBeanBuilder正如其名字定义的那样,实现了NamingServiceBeanBuilderBean的注册,在前面章节中曾提到过NamingServiceBeanBuilder用来创建NamingService实现类的对象。

public class NamingServiceBeanBuilder
		extends AbstractNacosServiceBeanBuilder<NamingService> {

	/**
	 * The bean name of {@link NamingServiceBeanBuilder}
	 */
	public static final String BEAN_NAME = "namingServiceBeanBuilder";

	public NamingServiceBeanBuilder() {
		super(GlobalNacosPropertiesSource.DISCOVERY);
	}

	@Override
	protected NamingService createService(NacosServiceFactory nacosServiceFactory,
			Properties properties) throws NacosException {
		return nacosServiceFactory.createNamingService(properties);
	}
}

NamingServiceBeanBuilder继承了AbstractNacosServiceBeanBuilder,并实现了createService方法,其内部通过nacosServiceFactory.createNamingService(properties)调用来创建NamingService实现类的对象,最终实现上是调用了NamingCreateWorker类的run方法。

public NamingService createNamingService(Properties properties)
    throws NacosException {
    Properties copy = new Properties();
    copy.putAll(properties);
    return (NamingService) createWorkerManager.get(ServiceType.NAMING).run(copy,
                                                                           null);
}

class NamingCreateWorker extends AbstractCreateWorker<NamingService> {

    NamingCreateWorker() {
    }

    @Override
    public NamingService run(Properties properties, NamingService service)
        throws NacosException {
        String cacheKey = identify(properties);
        NamingService namingService = namingServicesCache.get(cacheKey);

        if (namingService == null) {
            if (service == null) {
                service = NacosFactory.createNamingService(properties);
            }
            namingService = new DelegatingNamingService(service, properties);
            namingServicesCache.put(cacheKey, namingService);
        }
        return namingService;
    }
}

public static String identify(Map<?, ?> properties) {

    String namespace = (String) properties.get(NAMESPACE);
    String serverAddress = (String) properties.get(SERVER_ADDR);
    String contextPath = (String) properties.get(CONTEXT_PATH);
    String clusterName = (String) properties.get(CLUSTER_NAME);
    String endpoint = (String) properties.get(ENDPOINT);
    String accessKey = (String) properties.get(ACCESS_KEY);
    String secretKey = (String) properties.get(SECRET_KEY);
    String encode = (String) properties.get(ENCODE);

    return build(namespace, clusterName, serverAddress, contextPath, endpoint,
                 accessKey, secretKey, encode);

}

首先,会调用identify方法基于properties参数计算出唯一标识,这样相同的属性配置即使在多处使用,也会使用一个NamingService,避免重复创建,唯一标识就通过namespaceserverAddresscontextPathclusterNameendpointaccessKeysecretKeyencode来唯一确定。在计算出缓存Key后,就从namingServicesCache缓存中获取对象,如果获取不到,则调用NacosFactory.createNamingService(properties)创建,创建好的NamingService是Nacos客户端原生的Service,在真正使用时对其进行包装,包装成DelegatingNamingService对象,放入缓存中并返回。DelegatingNamingService与原生的相比,增加获取NamingService元数据getProperties()以及实例对象销毁时会自动关闭NamingServicedestroy()方法。

class DelegatingNamingService
		implements NamingService, NacosServiceMetaData, DisposableBean {

	private final NamingService delegate;

	private final Properties properties;

	DelegatingNamingService(NamingService delegate, Properties properties) {
		this.delegate = delegate;
		this.properties = properties;
	}

	@Override
	public void registerInstance(String serviceName, String ip, int port)
			throws NacosException {
		delegate.registerInstance(serviceName, ip, port);
	}

    ...

	@Override
	public void shutDown() throws NacosException {
		delegate.shutDown();
	}

	@Override
	public Properties getProperties() {
		return properties;
	}

	/**
	 * Destroy lifecycle method to invoke {@link #shutDown()}
	 * @throws Exception
	 * @since 1.0.0
	 */
	@Override
	public void destroy() throws Exception {
		shutDown();
	}
}

3.3.3.2 registerNamingMaintainServiceBeanBuilder

registerNamingMaintainServiceBeanBuilder实现了NamingMaintainServiceBeanBuilderBean的注册,用于NamingMaintainService对象的创建。NamingMaintainService同样是通过调用createService来创建,最终实现上是调用了MaintainCreateWorker类的run方法。

public class NamingMaintainServiceBeanBuilder
		extends AbstractNacosServiceBeanBuilder<NamingMaintainService> {

	/**
	 * The bean name of {@link NamingServiceBeanBuilder}
	 */
	public static final String BEAN_NAME = "namingMaintainServiceBeanBuilder";

	public NamingMaintainServiceBeanBuilder() {
		super(GlobalNacosPropertiesSource.MAINTAIN);
	}

	@Override
	protected NamingMaintainService createService(NacosServiceFactory nacosServiceFactory,
			Properties properties) throws NacosException {
		return nacosServiceFactory.createNamingMaintainService(properties);
	}
}

public NamingMaintainService createNamingMaintainService(Properties properties)
    throws NacosException {
    Properties copy = new Properties();
    copy.putAll(properties);
    return (NamingMaintainService) createWorkerManager.get(ServiceType.MAINTAIN)
        .run(copy, null);
}

class MaintainCreateWorker extends AbstractCreateWorker<NamingMaintainService> {

    MaintainCreateWorker() {
    }

    @Override
    public NamingMaintainService run(Properties properties,
                                     NamingMaintainService service) throws NacosException {
        String cacheKey = identify(properties);
        NamingMaintainService namingMaintainService = maintainServiceCache
            .get(cacheKey);

        if (namingMaintainService == null) {
            if (service == null) {
                service = NacosFactory.createMaintainService(properties);
            }
            namingMaintainService = new DelegatingNamingMaintainService(service,
                                                                        properties);
            maintainServiceCache.put(cacheKey, namingMaintainService);
        }
        return namingMaintainService;
    }
}

NamingService的类似,不再多说,我们来看看NamingMaintainServiceDelegatingNamingMaintainService的差异,只多了获取元数据getProperties()的方法。

class DelegatingNamingMaintainService
		implements NamingMaintainService, NacosServiceMetaData {

	private final NamingMaintainService delegate;

	private final Properties properties;

	DelegatingNamingMaintainService(NamingMaintainService delegate,
			Properties properties) {
		this.delegate = delegate;
		this.properties = properties;
	}

	@Override
	public void updateInstance(String serviceName, Instance instance)
			throws NacosException {
		delegate.updateInstance(serviceName, instance);
	}

	...
	
	@Override
	public Properties getProperties() {
		return properties;
	}
}

3.4 触发NacosPropertySourcePostProcessor调用

这一步会主动触发NacosPropertySourcePostProcessor的调用,实现NacosPropertySource的处理。首先会从Spring容器中获取NacosPropertySourcePostProcessor对象postProcessor,而后会调用postProcessBeanFactory方法触发NacosPropertySource的处理。当然,这里主动触发只会提前NacosPropertySource的处理,后续Spring还会触发NacosPropertySourcePostProcessor#postProcessBeanFactory,为何这么处理倒没看明白,不过提前调用也没啥问题。

public static void invokeNacosPropertySourcePostProcessor(BeanFactory beanFactory) {
    NacosPropertySourcePostProcessor postProcessor = beanFactory.getBean(
        NacosPropertySourcePostProcessor.BEAN_NAME,
        NacosPropertySourcePostProcessor.class);
    postProcessor
        .postProcessBeanFactory((ConfigurableListableBeanFactory) beanFactory);
}

四、参考资料