一、前言
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件事:
- 注册
PropertySourcesPlaceholderConfigurerBeanDefinition,后面用于生成相关Bean。 - 读取
@EnableNacos注解内的属性,作为全局属性Bean注册到Spring容器中。 - 注册被Nacos相关注解修饰相关类的Bean Definition,后面用于生成相关Bean,此外,实现依赖自动注入,外部化配置以及事件驱动。
- 创建
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类,并分别注册了NacosAnnotationDrivenBeanDefinitionParser,GlobalNacosPropertiesBeanDefinitionParser和NacosPropertySourceBeanDefinitionParser,分别实现上面介绍的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。在实现上首先会调用registerNacosPropertySourcePostProcessor和registerXmlNacosPropertySourceBuilder方法,向容器中注入所依赖的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.handlers和spring.schmas配置
spring.handlers和spring.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方法,创建一个名为globalNacosProperties的Properties对象,作为全局性的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)来获取被注入对象,并放入到缓存中。当然,buildInjectedObjectCacheKey和doGetInjectedBean在AbstractAnnotationBeanPostProcessor类中是两个抽象方法,其具体实现在其子类中。
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类对buildInjectedObjectCacheKey和doGetInjectedBean的具体实现。
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属性值,并调用AbstractNacosServiceBeanBuilder的build方法来创建要注入的对象。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,避免重复创建,唯一标识就通过namespace、serverAddress、contextPath、clusterName、endpoint、accessKey、secretKey和encode来唯一确定。在计算出缓存Key后,会从namingServicesCache缓存中获取对象,如果获取不到,则调用NacosFactory.createConfigService(properties)创建,创建好的ConfigService是Nacos客户端原生的Service,在真正使用时对其进行包装,包装成EventPublishingConfigService对象,放入缓存中并返回。EventPublishingConfigService重写了ConfigService所有接口,并做了下面功能的增强:
- 对监听器
Listener进行代理包装成DelegatingEventPublishingListener对象,实现配置变更后的自动刷新。 - 增加监听器注册、配置发布、配置移除、监听器移除等事件发布,用于日志记录。
- 增加获取
ConfigService元数据的getProperties()方法。 - 增加实例销毁时自动清理的
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已被加载刷新之后,但在ApplicationRunner和CommandLineRunner调用之前触发。也就是说,在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类对于isCandidateMethod和processListenerMethod方法的实现。
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的所有子类(AnnotationNacosPropertySourceBuilder和XmlNacosPropertySourceBuilder),存入到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方法中跳过的resolveRuntimeAttributesArray和doBuild方法。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。该方法会分别调用doWithFields和doWithMethods方法,获取被@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()以及@NacosValue的attributes拼接成唯一键。
@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,避免重复创建,唯一标识就通过namespace、serverAddress、contextPath、clusterName、endpoint、accessKey、secretKey和encode来唯一确定。在计算出缓存Key后,就从namingServicesCache缓存中获取对象,如果获取不到,则调用NacosFactory.createNamingService(properties)创建,创建好的NamingService是Nacos客户端原生的Service,在真正使用时对其进行包装,包装成DelegatingNamingService对象,放入缓存中并返回。DelegatingNamingService与原生的相比,增加获取NamingService元数据getProperties()以及实例对象销毁时会自动关闭NamingService的destroy()方法。
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的类似,不再多说,我们来看看NamingMaintainService和DelegatingNamingMaintainService的差异,只多了获取元数据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);
}