From effabf43a8d3b4eb421c53ea932e13f559747656 Mon Sep 17 00:00:00 2001 From: shiyiyue1102 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 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 @@ private String getGroupKeyContent(String dataId, String group) throws Exception } 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 String toString() { @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 @@ private void handleFiledAnnotation(Object bean, String beanName, Field field) { } } + 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 String toString() { } }; 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 String toString() { }; } - 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 String toString() { }; } - nacosConfigManager.getConfigService() + getNacosConfigManager().getConfigService() .addListener(dataId, group, listener); targetListenerMap.put(refreshTargetKey, listener); @@ -500,7 +598,7 @@ public String toString() { }; } - nacosConfigManager.getConfigService() + getNacosConfigManager().getConfigService() .addListener(dataId, group, listener); targetListenerMap.put(refreshTargetKey, listener); return true; @@ -606,12 +704,58 @@ private void handleMethodAnnotation(final Object bean, String beanName, final Me 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 nacosConfigMergedAnnotation = annotations.get(NacosConfig.class); + Map 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 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 @@ * @author shiyiyue1102 */ @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD}) +@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD}) @Documented public @interface NacosConfig {