From effabf43a8d3b4eb421c53ea932e13f559747656 Mon Sep 17 00:00:00 2001
From: shiyiyue1102 <liuzunfei@gmail.com>
Date: Tue, 26 Nov 2024 14:34:17 +0800
Subject: [PATCH] nacos config support type and bean factory (#3899)

---
 .../annotation/NacosAnnotationProcessor.java  | 160 +++++++++++++++++-
 .../cloud/nacos/annotation/NacosConfig.java   |   2 +-
 2 files changed, 153 insertions(+), 9 deletions(-)

diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosAnnotationProcessor.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosAnnotationProcessor.java
index 887a58cbd..d74af53e3 100644
--- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosAnnotationProcessor.java
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosAnnotationProcessor.java
@@ -16,6 +16,7 @@
 
 package com.alibaba.cloud.nacos.annotation;
 
+import java.beans.PropertyDescriptor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
@@ -37,12 +38,22 @@ import com.alibaba.nacos.client.config.common.GroupKey;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeanWrapperImpl;
 import org.springframework.beans.BeansException;
+import org.springframework.beans.NotReadablePropertyException;
+import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
+import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.support.GenericApplicationContext;
 import org.springframework.core.PriorityOrdered;
 import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.MergedAnnotation;
+import org.springframework.core.annotation.MergedAnnotations;
+import org.springframework.core.type.MethodMetadata;
 import org.springframework.util.ReflectionUtils;
 
 public class NacosAnnotationProcessor implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware {
@@ -68,12 +79,12 @@ public class NacosAnnotationProcessor implements BeanPostProcessor, PriorityOrde
 		}
 		synchronized (this) {
 			if (!groupKeyCache.containsKey(GroupKey.getKey(dataId, group))) {
-				String content = nacosConfigManager.getConfigService().getConfig(dataId, group, 5000);
+				String content = getNacosConfigManager().getConfigService().getConfig(dataId, group, 5000);
 				groupKeyCache.put(GroupKey.getKey(dataId, group), new AtomicReference<>(content));
 
 				log.info("[Nacos Config] Listening config for annotation: dataId={}, group={}", dataId,
 						group);
-				nacosConfigManager.getConfigService().addListener(dataId, group, new AbstractListener() {
+				getNacosConfigManager().getConfigService().addListener(dataId, group, new AbstractListener() {
 					@Override
 					public void receiveConfigInfo(String s) {
 						groupKeyCache.get(GroupKey.getKey(dataId, group)).set(s);
@@ -94,13 +105,20 @@ public class NacosAnnotationProcessor implements BeanPostProcessor, PriorityOrde
 
 	@Override
 	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
-		return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
+		BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
+		return bean;
 	}
 
 	@Override
 	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 		BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
 		Class clazz = bean.getClass();
+		NacosConfig annotationBean = AnnotationUtils.findAnnotation(clazz, NacosConfig.class);
+		if (annotationBean != null) {
+			handleBeanNacosConfigAnnotation(annotationBean.dataId(), annotationBean.group(), annotationBean.key(), beanName, bean, annotationBean.defaultValue());
+			return bean;
+		}
+
 		for (Field field : getBeanFields(clazz)) {
 			handleFiledAnnotation(bean, beanName, field);
 		}
@@ -129,6 +147,86 @@ public class NacosAnnotationProcessor implements BeanPostProcessor, PriorityOrde
 		}
 	}
 
+	private void handleBeanNacosConfigAnnotation(String dataId, String group, String key, String beanName, Object bean,
+			String defaultValue) {
+		try {
+			String config = getDestContent(getGroupKeyContent(dataId, group), key);
+			if (!org.springframework.util.StringUtils.hasText(config)) {
+				config = defaultValue;
+			}
+			//Init bean properties.
+			if (org.springframework.util.StringUtils.hasText(config)) {
+				Object targetObject = convertContentToTargetType(config, bean.getClass());
+				//yaml and json to object
+				BeanUtils.copyProperties(targetObject, bean, getNullPropertyNames(targetObject));
+			}
+			String refreshTargetKey = beanName + "#instance#";
+			TargetRefreshable currentTarget = targetListenerMap.get(refreshTargetKey);
+			if (currentTarget != null) {
+				log.info("[Nacos Config] reset {} listener from  {} to {} ", refreshTargetKey,
+						currentTarget.getTarget(), bean);
+				targetListenerMap.get(refreshTargetKey).setTarget(bean);
+				return;
+			}
+			log.info("[Nacos Config] register {} listener on {} ", refreshTargetKey,
+					bean);
+			TargetRefreshable listener = null;
+			if (org.springframework.util.StringUtils.hasText(key)) {
+				listener = new NacosPropertiesKeyListener(bean, wrapArrayToSet(key)) {
+					@Override
+					public void configChanged(ConfigChangeEvent event) {
+						try {
+							ConfigChangeItem changeItem = event.getChangeItem(key);
+							String newConfig = changeItem == null ? null : changeItem.getNewValue();
+							if (!org.springframework.util.StringUtils.hasText(newConfig)) {
+								newConfig = defaultValue;
+							}
+							if (org.springframework.util.StringUtils.hasText(newConfig)) {
+								Object targetObject = convertContentToTargetType(newConfig, getTarget().getClass());
+								//yaml and json to object
+								BeanUtils.copyProperties(targetObject, getTarget(), getNullPropertyNames(targetObject));
+							}
+						}
+						catch (Exception e) {
+							throw new RuntimeException(e);
+						}
+					}
+
+					@Override
+					public String toString() {
+						return String.format("[spring cloud alibaba nacos config instance key listener , key %s , target %s ] ", key, bean);
+					}
+				};
+			}
+			else {
+				listener = new NacosConfigRefreshableListener(bean) {
+					@Override
+					public void receiveConfigInfo(String configInfo) {
+						if (!org.springframework.util.StringUtils.hasText(configInfo)) {
+							configInfo = defaultValue;
+						}
+						if (org.springframework.util.StringUtils.hasText(configInfo)) {
+							Object targetObject = convertContentToTargetType(configInfo, bean.getClass());
+							//yaml and json to object
+							BeanUtils.copyProperties(targetObject, getTarget());
+						}
+					}
+
+					@Override
+					public String toString() {
+						return String.format("[spring cloud alibaba nacos config instance  listener , key %s , target %s ] ", key, bean);
+					}
+				};
+			}
+			getNacosConfigManager().getConfigService()
+					.addListener(dataId, group, listener);
+			targetListenerMap.put(refreshTargetKey, listener);
+		}
+		catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+	}
+
 	private void handleMethodNacosConfigKeysChangeListener(NacosConfigKeysListener annotation, String beanName, Object bean,
 			Method method) {
 		String dataId = annotation.dataId();
@@ -166,7 +264,7 @@ public class NacosAnnotationProcessor implements BeanPostProcessor, PriorityOrde
 				}
 			};
 			nacosPropertiesKeyListener.setLastContent(getGroupKeyContent(dataId, group));
-			nacosConfigManager.getConfigService().addListener(dataId, group,
+			getNacosConfigManager().getConfigService().addListener(dataId, group,
 					nacosPropertiesKeyListener);
 			targetListenerMap.put(refreshTargetKey, nacosPropertiesKeyListener);
 		}
@@ -278,7 +376,7 @@ public class NacosAnnotationProcessor implements BeanPostProcessor, PriorityOrde
 				};
 			}
 
-			nacosConfigManager.getConfigService().addListener(dataId, group, listener);
+			getNacosConfigManager().getConfigService().addListener(dataId, group, listener);
 			targetListenerMap.put(refreshTargetKey, listener);
 			if (annotation.initNotify() && org.springframework.util.StringUtils.hasText(configInfo)) {
 				try {
@@ -415,7 +513,7 @@ public class NacosAnnotationProcessor implements BeanPostProcessor, PriorityOrde
 				};
 			}
 
-			nacosConfigManager.getConfigService()
+			getNacosConfigManager().getConfigService()
 					.addListener(dataId, group, listener);
 			targetListenerMap.put(refreshTargetKey, listener);
 
@@ -500,7 +598,7 @@ public class NacosAnnotationProcessor implements BeanPostProcessor, PriorityOrde
 				};
 			}
 
-			nacosConfigManager.getConfigService()
+			getNacosConfigManager().getConfigService()
 					.addListener(dataId, group, listener);
 			targetListenerMap.put(refreshTargetKey, listener);
 			return true;
@@ -606,12 +704,58 @@ public class NacosAnnotationProcessor implements BeanPostProcessor, PriorityOrde
 			handleMethodNacosConfigListener(configAnnotation, beanName, bean, method);
 			return;
 		}
+		if (!applicationContext.containsBeanDefinition(beanName)) {
+			return;
+		}
+		BeanDefinition beanDefinition = ((GenericApplicationContext) applicationContext).getBeanDefinition(beanName);
+		if (beanDefinition instanceof AnnotatedBeanDefinition) {
+			MethodMetadata factoryMethodMetadata = (((AnnotatedBeanDefinition) beanDefinition).getFactoryMethodMetadata());
+			if (factoryMethodMetadata != null) {
+				MergedAnnotations annotations = factoryMethodMetadata.getAnnotations();
+				if (annotations != null && annotations.isPresent(NacosConfig.class)) {
+					MergedAnnotation<NacosConfig> nacosConfigMergedAnnotation = annotations.get(NacosConfig.class);
+					Map<String, Object> stringObjectMap = nacosConfigMergedAnnotation.asMap();
+					String dataId = (String) stringObjectMap.get("dataId");
+					String group = (String) stringObjectMap.get("group");
+					String key = (String) stringObjectMap.get("key");
+					String defaultValue = (String) stringObjectMap.get("defaultValue");
+					handleBeanNacosConfigAnnotation(dataId, group, key, beanName, bean, defaultValue);
+				}
+			}
+		}
 
 	}
 
 	@Override
 	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
 		this.applicationContext = applicationContext;
-		nacosConfigManager = this.applicationContext.getBean(NacosConfigManager.class);
 	}
+
+	private NacosConfigManager getNacosConfigManager() {
+		if (nacosConfigManager == null) {
+			nacosConfigManager = this.applicationContext.getBean(NacosConfigManager.class);
+		}
+		return nacosConfigManager;
+	}
+
+	private static String[] getNullPropertyNames(Object source) {
+		final BeanWrapper src = new BeanWrapperImpl(source);
+		PropertyDescriptor[] pds = src.getPropertyDescriptors();
+		Set<String> nullPropertyNames = new HashSet<>();
+		for (PropertyDescriptor pd : pds) {
+			String propertyName = pd.getName();
+			try {
+				Object propertyValue = src.getPropertyValue(propertyName);
+				if (propertyValue == null) {
+					nullPropertyNames.add(propertyName);
+				}
+			}
+			catch (NotReadablePropertyException e) {
+				//ignore
+				nullPropertyNames.add(propertyName);
+			}
+		}
+		return nullPropertyNames.toArray(new String[0]);
+	}
+
 }
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosConfig.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosConfig.java
index 1799803b4..37fa1face 100644
--- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosConfig.java
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosConfig.java
@@ -28,7 +28,7 @@ import java.lang.annotation.Target;
  * @author shiyiyue1102
  */
 @Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.FIELD})
+@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
 @Documented
 public @interface NacosConfig {