support nacos config annotation (#3856)
parent
2902295dd8
commit
d93217f108
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.cloud.nacos.annotation;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.nacos.api.config.ConfigChangeEvent;
|
||||
import com.alibaba.nacos.api.config.ConfigChangeItem;
|
||||
import com.alibaba.nacos.api.config.listener.AbstractSharedListener;
|
||||
import com.alibaba.nacos.client.config.impl.ConfigChangeHandler;
|
||||
|
||||
public abstract class AbstractConfigChangeListener extends AbstractSharedListener implements TargetRefreshable {
|
||||
|
||||
String lastContent;
|
||||
|
||||
Object target;
|
||||
|
||||
@Override
|
||||
public Object getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTarget(Object target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public AbstractConfigChangeListener(Object target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
protected void setLastContent(String lastContent) {
|
||||
this.lastContent = lastContent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void innerReceive(String dataId, String group, String configInfo) {
|
||||
|
||||
Map<String, ConfigChangeItem> data = null;
|
||||
try {
|
||||
data = ConfigChangeHandler.getInstance().parseChangeData(lastContent, configInfo, type(dataId));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
ConfigChangeEvent event = new ConfigChangeEvent(data);
|
||||
receiveConfigChange(event);
|
||||
lastContent = configInfo;
|
||||
}
|
||||
|
||||
private String type(String dataId) {
|
||||
if (dataId.endsWith(".yml") || dataId.endsWith(".yaml")) {
|
||||
return "yaml";
|
||||
}
|
||||
return "properties";
|
||||
}
|
||||
|
||||
abstract void receiveConfigChange(ConfigChangeEvent event);
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.cloud.nacos.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
public class CustomDateDeserializer extends JsonDeserializer<Date> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public CustomDateDeserializer() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
|
||||
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
|
||||
String date = node.textValue();
|
||||
try {
|
||||
return dateFormat.parse(date);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IOException("Invalid date format");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.cloud.nacos.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import com.alibaba.nacos.api.exception.runtime.NacosDeserializationException;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
|
||||
final class JsonUtils {
|
||||
|
||||
private JsonUtils() {
|
||||
}
|
||||
|
||||
static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Json string deserialize to Object.
|
||||
*
|
||||
* @param json json string
|
||||
* @param cls class of object
|
||||
* @param <T> General type
|
||||
* @return object
|
||||
* @throws NacosDeserializationException if deserialize failed
|
||||
*/
|
||||
public static <T> T toObj(String json, Class<T> cls) {
|
||||
try {
|
||||
return mapper.readValue(json, cls);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new NacosDeserializationException(cls, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T toObj(String json, Type type) {
|
||||
try {
|
||||
return mapper.readValue(json, TypeFactory.defaultInstance().constructType(type));
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new NacosDeserializationException(type, e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,617 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.cloud.nacos.annotation;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.alibaba.cloud.nacos.NacosConfigManager;
|
||||
import com.alibaba.nacos.api.config.ConfigChangeEvent;
|
||||
import com.alibaba.nacos.api.config.ConfigChangeItem;
|
||||
import com.alibaba.nacos.api.config.listener.AbstractListener;
|
||||
import com.alibaba.nacos.client.config.common.GroupKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
public class NacosAnnotationProcessor implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware {
|
||||
|
||||
private NacosConfigManager nacosConfigManager;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private final static Logger log = LoggerFactory
|
||||
.getLogger(NacosAnnotationProcessor.class);
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private Map<String, TargetRefreshable> targetListenerMap = new ConcurrentHashMap<>();
|
||||
private Map<String, AtomicReference<String>> groupKeyCache = new ConcurrentHashMap<>();
|
||||
|
||||
private String getGroupKeyContent(String dataId, String group) throws Exception {
|
||||
if (groupKeyCache.containsKey(GroupKey.getKey(dataId, group))) {
|
||||
return groupKeyCache.get(GroupKey.getKey(dataId, group)).get();
|
||||
}
|
||||
synchronized (this) {
|
||||
if (!groupKeyCache.containsKey(GroupKey.getKey(dataId, group))) {
|
||||
String content = nacosConfigManager.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() {
|
||||
@Override
|
||||
public void receiveConfigInfo(String s) {
|
||||
groupKeyCache.get(GroupKey.getKey(dataId, group)).set(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("sca nacos config annotation cache config listener");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return groupKeyCache.get(GroupKey.getKey(dataId, group)).get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
|
||||
Class clazz = bean.getClass();
|
||||
for (Field field : getBeanFields(clazz)) {
|
||||
handleFiledAnnotation(bean, beanName, field);
|
||||
}
|
||||
for (Method method : getBeanMethods(clazz)) {
|
||||
handleMethodAnnotation(bean, beanName, method);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
private List<Field> getBeanFields(Class clazz) {
|
||||
List<Field> res = new ArrayList<>();
|
||||
ReflectionUtils.doWithFields(clazz, field -> res.add(field));
|
||||
return res;
|
||||
}
|
||||
|
||||
private List<Method> getBeanMethods(Class clazz) {
|
||||
List<Method> res = new ArrayList<>();
|
||||
ReflectionUtils.doWithMethods(clazz, method -> res.add(method));
|
||||
return res;
|
||||
}
|
||||
|
||||
private void handleFiledAnnotation(Object bean, String beanName, Field field) {
|
||||
NacosConfig annotation = AnnotationUtils.getAnnotation(field, NacosConfig.class);
|
||||
if (annotation != null) {
|
||||
handleFiledNacosConfigAnnotation(annotation, beanName, bean, field);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMethodNacosConfigKeysChangeListener(NacosConfigKeysListener annotation, String beanName, Object bean,
|
||||
Method method) {
|
||||
String dataId = annotation.dataId();
|
||||
String group = annotation.group();
|
||||
try {
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
if (parameterTypes.length != 1 || !ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0])) {
|
||||
throw new RuntimeException(
|
||||
"NacosConfigKeysChangeListener must be marked as a single parameter with ConfigChangeEvent");
|
||||
}
|
||||
|
||||
String refreshTargetKey = beanName + "#method#" + methodSignature(method);
|
||||
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);
|
||||
// annotation on string.
|
||||
NacosPropertiesKeyListener nacosPropertiesKeyListener = new NacosPropertiesKeyListener(bean, wrapArrayToSet(annotation.interestedKeys()),
|
||||
wrapArrayToSet(annotation.interestedKeyPrefixes())) {
|
||||
|
||||
@Override
|
||||
public void configChanged(ConfigChangeEvent event) {
|
||||
ReflectionUtils.invokeMethod(method, this.getTarget(), event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("sca nacos config listener on bean method %s", bean + "#" + methodSignature(method));
|
||||
}
|
||||
};
|
||||
nacosPropertiesKeyListener.setLastContent(getGroupKeyContent(dataId, group));
|
||||
nacosConfigManager.getConfigService().addListener(dataId, group,
|
||||
nacosPropertiesKeyListener);
|
||||
targetListenerMap.put(refreshTargetKey, nacosPropertiesKeyListener);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> wrapArrayToSet(String... arrayKeys) {
|
||||
return new HashSet<>(Arrays.asList(arrayKeys));
|
||||
}
|
||||
|
||||
private String methodSignature(Method method) {
|
||||
StringBuilder signature = new StringBuilder(method.getName() + "(");
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
signature.append(parameterTypes[i].getSimpleName());
|
||||
if (i < parameterTypes.length - 1) {
|
||||
signature.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
signature.append(")");
|
||||
return signature.toString();
|
||||
}
|
||||
|
||||
private void handleMethodNacosConfigListener(NacosConfigListener annotation, String beanName, Object bean, Method method) {
|
||||
String dataId = annotation.dataId();
|
||||
String group = annotation.group();
|
||||
String key = annotation.key();
|
||||
try {
|
||||
Type[] parameterTypes = method.getGenericParameterTypes();
|
||||
if (parameterTypes.length != 1) {
|
||||
throw new RuntimeException(
|
||||
"@NacosConfigListener must be over a method with a single parameter");
|
||||
}
|
||||
|
||||
String configInfo = getGroupKeyContent(dataId, group);
|
||||
String refreshTargetKey = beanName + "#method#" + methodSignature(method);
|
||||
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)) {
|
||||
if (invokePrimitiveMethod(method, getTarget(), newConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object targetObject = convertContentToTargetType(newConfig, parameterTypes[0]);
|
||||
ReflectionUtils.invokeMethod(method, getTarget(), targetObject);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[spring cloud alibaba nacos config key listener , key %s , target %s ] ", key, bean + "#" + methodSignature(method));
|
||||
}
|
||||
};
|
||||
((AbstractConfigChangeListener) listener).fillContext(dataId, group);
|
||||
if (!annotation.initNotify()) {
|
||||
((AbstractConfigChangeListener) listener).setLastContent(configInfo);
|
||||
}
|
||||
}
|
||||
else {
|
||||
listener = new NacosConfigRefreshableListener(bean) {
|
||||
|
||||
@Override
|
||||
public void receiveConfigInfo(String configInfo) {
|
||||
if (org.springframework.util.StringUtils.hasText(configInfo)) {
|
||||
try {
|
||||
if (invokePrimitiveMethod(method, getTarget(), configInfo)) {
|
||||
return;
|
||||
}
|
||||
Object targetObject = convertContentToTargetType(configInfo, parameterTypes[0]);
|
||||
ReflectionUtils.invokeMethod(method, getTarget(), targetObject);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[spring cloud alibaba nacos config listener , target %s ] ", bean + "#" + methodSignature(method));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
nacosConfigManager.getConfigService().addListener(dataId, group, listener);
|
||||
targetListenerMap.put(refreshTargetKey, listener);
|
||||
if (annotation.initNotify() && org.springframework.util.StringUtils.hasText(configInfo)) {
|
||||
try {
|
||||
log.info("[Nacos Config] init notify listener of {} on {} start...", refreshTargetKey,
|
||||
bean);
|
||||
listener.receiveConfigInfo(configInfo);
|
||||
log.info("[Nacos Config] init notify listener of {} on {} finished ", refreshTargetKey,
|
||||
bean);
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
log.warn("[Nacos Config] init notify listener error", throwable);
|
||||
throw throwable;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Object convertContentToTargetType(String rawContent, Type type) {
|
||||
|
||||
if (String.class.getCanonicalName().equals(type.getTypeName())) {
|
||||
return rawContent;
|
||||
}
|
||||
|
||||
if (Properties.class.getCanonicalName().equals(type.getTypeName())) {
|
||||
//properties and yaml config to properties.
|
||||
Properties properties = new Properties();
|
||||
try {
|
||||
if (org.springframework.util.StringUtils.hasText(rawContent)) {
|
||||
properties = PropertiesUtils.convertToProperties(rawContent);
|
||||
}
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
throw new RuntimeException(throwable);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
return ObjectUtils.convertToObject(rawContent, type);
|
||||
}
|
||||
|
||||
private void handleFiledNacosConfigAnnotation(NacosConfig annotation, String beanName, Object bean, Field field) {
|
||||
String dataId = annotation.dataId();
|
||||
String group = annotation.group();
|
||||
String key = annotation.key();
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
handleFiledNacosConfigAnnotationInner(dataId, group, key, beanName, bean, field, annotation.defaultValue());
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFiledNacosConfigAnnotationInner(String dataId, String group, String key, String beanName, Object bean,
|
||||
Field field, String defaultValue) {
|
||||
try {
|
||||
String config = getDestContent(getGroupKeyContent(dataId, group), key);
|
||||
if (!org.springframework.util.StringUtils.hasText(config)) {
|
||||
config = defaultValue;
|
||||
}
|
||||
|
||||
//primitive type
|
||||
if (handPrimitiveFiled(field, dataId, group, config, key, defaultValue, beanName, bean)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//for other type.
|
||||
if (org.springframework.util.StringUtils.hasText(config)) {
|
||||
Object targetObject = convertContentToTargetType(config, field.getGenericType());
|
||||
//yaml and json to object
|
||||
ReflectionUtils.setField(field, bean, targetObject);
|
||||
}
|
||||
|
||||
String refreshTargetKey = beanName + "#filed#" + field.getName();
|
||||
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, field.getGenericType());
|
||||
ReflectionUtils.setField(field, getTarget(), targetObject);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[spring cloud alibaba nacos config key listener , key %s , target %s ] ", key, bean + "#" + field.getName());
|
||||
}
|
||||
};
|
||||
}
|
||||
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, field.getGenericType());
|
||||
ReflectionUtils.setField(field, getTarget(), targetObject);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[spring cloud alibaba nacos config key listener , key %s , target %s ] ", key, bean + "#" + field.getName());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
nacosConfigManager.getConfigService()
|
||||
.addListener(dataId, group, listener);
|
||||
targetListenerMap.put(refreshTargetKey, listener);
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handPrimitiveFiled(Field field, String dataId, String group, String config, String key, String defaultValue, String beanName, Object bean) throws Exception {
|
||||
if (field.getType().isPrimitive()) {
|
||||
|
||||
if (org.springframework.util.StringUtils.hasText(config)) {
|
||||
try {
|
||||
setPrimitiveFiled(field, bean, config);
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
throw new RuntimeException(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
String refreshTargetKey = beanName + "#filed#" + field.getName();
|
||||
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 true;
|
||||
}
|
||||
|
||||
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)) {
|
||||
setPrimitiveFiled(field, getTarget(), newConfig);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[spring cloud alibaba nacos config key listener , key %s , target %s ] ", key, bean + "#" + field.getName());
|
||||
}
|
||||
};
|
||||
}
|
||||
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)) {
|
||||
try {
|
||||
setPrimitiveFiled(field, getTarget(), configInfo);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[spring cloud alibaba nacos config key listener , key %s , target %s ] ", key, bean + "#" + field.getName());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
nacosConfigManager.getConfigService()
|
||||
.addListener(dataId, group, listener);
|
||||
targetListenerMap.put(refreshTargetKey, listener);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean setPrimitiveFiled(Field filed, Object bean, String value) throws Exception {
|
||||
if (filed.getType() == int.class) {
|
||||
filed.setInt(bean, Integer.parseInt(value));
|
||||
}
|
||||
else if (filed.getType() == Integer.class) {
|
||||
ReflectionUtils.setField(filed, bean, Integer.valueOf(value));
|
||||
}
|
||||
else if (filed.getType() == long.class) {
|
||||
filed.setLong(bean, Long.parseLong(value));
|
||||
}
|
||||
else if (filed.getType() == Long.class) {
|
||||
ReflectionUtils.setField(filed, bean, Long.valueOf(value));
|
||||
}
|
||||
else if (filed.getType() == boolean.class) {
|
||||
filed.setBoolean(bean, Boolean.parseBoolean(value));
|
||||
}
|
||||
else if (filed.getType() == Boolean.class) {
|
||||
ReflectionUtils.setField(filed, bean, Boolean.valueOf(value));
|
||||
}
|
||||
else if (filed.getType() == double.class) {
|
||||
filed.setDouble(bean, Double.parseDouble(value));
|
||||
}
|
||||
else if (filed.getType() == Double.class) {
|
||||
ReflectionUtils.setField(filed, bean, Double.valueOf(value));
|
||||
}
|
||||
else if (filed.getType() == float.class) {
|
||||
filed.setFloat(bean, Float.parseFloat(value));
|
||||
}
|
||||
else if (filed.getType() == Float.class) {
|
||||
ReflectionUtils.setField(filed, bean, Float.valueOf(value));
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean invokePrimitiveMethod(Method method, Object bean, String value) throws Exception {
|
||||
Class<?> parameterType = method.getParameterTypes()[0];
|
||||
if (parameterType == int.class) {
|
||||
ReflectionUtils.invokeMethod(method, bean, Integer.parseInt(value));
|
||||
}
|
||||
else if (parameterType == Integer.class) {
|
||||
ReflectionUtils.invokeMethod(method, bean, Integer.valueOf(value));
|
||||
}
|
||||
else if (parameterType == long.class) {
|
||||
ReflectionUtils.invokeMethod(method, bean, Long.parseLong(value));
|
||||
}
|
||||
else if (parameterType == Long.class) {
|
||||
ReflectionUtils.invokeMethod(method, bean, Long.valueOf(value));
|
||||
}
|
||||
else if (parameterType == boolean.class) {
|
||||
ReflectionUtils.invokeMethod(method, bean, Boolean.parseBoolean(value));
|
||||
}
|
||||
else if (parameterType == Boolean.class) {
|
||||
ReflectionUtils.invokeMethod(method, bean, Boolean.valueOf(value));
|
||||
}
|
||||
else if (parameterType == double.class) {
|
||||
ReflectionUtils.invokeMethod(method, bean, Double.parseDouble(value));
|
||||
}
|
||||
else if (parameterType == Double.class) {
|
||||
ReflectionUtils.invokeMethod(method, bean, Double.valueOf(value));
|
||||
}
|
||||
else if (parameterType == float.class) {
|
||||
ReflectionUtils.invokeMethod(method, bean, Float.parseFloat(value));
|
||||
}
|
||||
else if (parameterType == Float.class) {
|
||||
ReflectionUtils.invokeMethod(method, bean, Float.valueOf(value));
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getDestContent(String content, String key) throws Exception {
|
||||
if (org.springframework.util.StringUtils.hasText(key)) {
|
||||
Properties properties = PropertiesUtils.convertToProperties(content);
|
||||
return properties.getProperty(key);
|
||||
}
|
||||
else {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMethodAnnotation(final Object bean, String beanName, final Method method) {
|
||||
NacosConfigKeysListener keysAnnotation = AnnotationUtils.getAnnotation(method, NacosConfigKeysListener.class);
|
||||
if (keysAnnotation != null) {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
handleMethodNacosConfigKeysChangeListener(keysAnnotation, beanName, bean, method);
|
||||
return;
|
||||
}
|
||||
NacosConfigListener configAnnotation = AnnotationUtils.getAnnotation(method, NacosConfigListener.class);
|
||||
if (configAnnotation != null) {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
handleMethodNacosConfigListener(configAnnotation, beanName, bean, method);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
nacosConfigManager = this.applicationContext.getBean(NacosConfigManager.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.cloud.nacos.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Nacos Config annotation.
|
||||
*
|
||||
* @author shiyiyue1102
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD})
|
||||
@Documented
|
||||
public @interface NacosConfig {
|
||||
|
||||
String group();
|
||||
|
||||
String dataId();
|
||||
|
||||
String key() default "";
|
||||
|
||||
String defaultValue() default "";
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.cloud.nacos.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @ConfigChangeEvent
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
public @interface NacosConfigKeysListener {
|
||||
|
||||
String dataId();
|
||||
|
||||
String group();
|
||||
|
||||
String[] interestedKeys() default {};
|
||||
|
||||
String[] interestedKeyPrefixes() default {};
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.cloud.nacos.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
public @interface NacosConfigListener {
|
||||
|
||||
String dataId();
|
||||
|
||||
String group();
|
||||
|
||||
String key() default "";
|
||||
|
||||
boolean initNotify() default false;
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.cloud.nacos.annotation;
|
||||
|
||||
import com.alibaba.nacos.api.config.listener.AbstractListener;
|
||||
|
||||
public abstract class NacosConfigRefreshableListener extends AbstractListener implements TargetRefreshable {
|
||||
|
||||
Object target;
|
||||
|
||||
NacosConfigRefreshableListener(Object target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public Object getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTarget(Object target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.cloud.nacos.annotation;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.nacos.api.config.ConfigChangeEvent;
|
||||
import com.alibaba.nacos.api.config.ConfigChangeItem;
|
||||
import com.alibaba.nacos.common.utils.CollectionUtils;
|
||||
|
||||
public abstract class NacosPropertiesKeyListener extends AbstractConfigChangeListener {
|
||||
|
||||
Set<String> interestedKeys;
|
||||
|
||||
Set<String> interestedKeyPrefixes;
|
||||
|
||||
NacosPropertiesKeyListener(Object target) {
|
||||
super(target);
|
||||
}
|
||||
|
||||
NacosPropertiesKeyListener(Object target, Set<String> interestedKeys) {
|
||||
this(target);
|
||||
this.interestedKeys = interestedKeys;
|
||||
}
|
||||
|
||||
public NacosPropertiesKeyListener(Object target, Set<String> interestedKeys, Set<String> interestedKeyPrefixes) {
|
||||
this(target);
|
||||
this.interestedKeys = interestedKeys;
|
||||
this.interestedKeyPrefixes = interestedKeyPrefixes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void receiveConfigChange(ConfigChangeEvent event) {
|
||||
if (CollectionUtils.isNotEmpty(interestedKeys) || CollectionUtils.isNotEmpty(interestedKeyPrefixes)) {
|
||||
boolean foundInterested = false;
|
||||
for (ConfigChangeItem changeItem : event.getChangeItems()) {
|
||||
if (interestedKeys != null && interestedKeys.contains(changeItem.getKey())) {
|
||||
foundInterested = true;
|
||||
break;
|
||||
}
|
||||
if (interestedKeyPrefixes != null) {
|
||||
for (String prefix : interestedKeyPrefixes) {
|
||||
if (changeItem.getKey().startsWith(prefix)) {
|
||||
foundInterested = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundInterested) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
configChanged(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NacosPropertiesKeyListener{" + "interestedKeys=" + interestedKeys + ", interestedKeyPrefixes="
|
||||
+ interestedKeyPrefixes + '}' + "@" + hashCode();
|
||||
}
|
||||
|
||||
public abstract void configChanged(ConfigChangeEvent event);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.cloud.nacos.annotation;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
final class ObjectUtils {
|
||||
|
||||
private ObjectUtils() {
|
||||
}
|
||||
|
||||
public static Object convertToObject(String content, Type clazz) {
|
||||
if (!StringUtils.hasText(content)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return convertFormJsonContent(content, clazz);
|
||||
}
|
||||
|
||||
private static Object convertFormJsonContent(String content, Type clazz) {
|
||||
|
||||
return JsonUtils.toObj(content, clazz);
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.cloud.nacos.annotation;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.alibaba.nacos.common.utils.StringUtils;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||
|
||||
final class PropertiesUtils {
|
||||
|
||||
private PropertiesUtils() {
|
||||
}
|
||||
|
||||
public static Properties convertToProperties(String content) throws Exception {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return new Properties();
|
||||
}
|
||||
try {
|
||||
return convertFormYamlContent(content);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return convertFormPropertiesContent(content);
|
||||
}
|
||||
}
|
||||
|
||||
private static Properties convertFormPropertiesContent(String content) throws Exception {
|
||||
Properties properties = new Properties();
|
||||
properties.load(new StringReader(content));
|
||||
return properties;
|
||||
}
|
||||
|
||||
private static Properties convertFormYamlContent(String content) {
|
||||
|
||||
Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
|
||||
Map<String, Object> yamlMap = yaml.load(content);
|
||||
|
||||
Properties properties = new Properties();
|
||||
flattenMap("", yamlMap, properties);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private static void flattenMap(String prefix, Map<String, Object> map, Properties properties) {
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
String key =
|
||||
prefix.isEmpty() ? String.valueOf(entry.getKey()) : prefix + "." + String.valueOf(entry.getKey());
|
||||
if (entry.getValue() instanceof Map) {
|
||||
flattenMap(key, (Map<String, Object>) entry.getValue(), properties);
|
||||
}
|
||||
else {
|
||||
properties.setProperty(key, entry.getValue().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.cloud.nacos.annotation;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.nacos.api.config.ConfigChangeItem;
|
||||
import com.alibaba.nacos.api.exception.NacosException;
|
||||
import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException;
|
||||
import com.alibaba.nacos.client.config.impl.YmlChangeParser;
|
||||
import com.alibaba.nacos.common.utils.StringUtils;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.composer.ComposerException;
|
||||
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||
import org.yaml.snakeyaml.error.MarkedYAMLException;
|
||||
|
||||
public class ScaYamlConfigChangeParser extends YmlChangeParser {
|
||||
private static final String INVALID_CONSTRUCTOR_ERROR_INFO = "could not determine a constructor for the tag";
|
||||
|
||||
public ScaYamlConfigChangeParser() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ConfigChangeItem> doParse(String oldContent, String newContent, String type) {
|
||||
Map<String, Object> oldMap = Collections.emptyMap();
|
||||
Map<String, Object> newMap = Collections.emptyMap();
|
||||
try {
|
||||
Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
|
||||
if (StringUtils.isNotBlank(oldContent)) {
|
||||
oldMap = yaml.load(oldContent);
|
||||
oldMap = getFlattenedMap(oldMap);
|
||||
}
|
||||
if (StringUtils.isNotBlank(newContent)) {
|
||||
newMap = yaml.load(newContent);
|
||||
newMap = getFlattenedMap(newMap);
|
||||
}
|
||||
}
|
||||
catch (MarkedYAMLException e) {
|
||||
handleYamlException(e);
|
||||
}
|
||||
|
||||
return filterChangeData(oldMap, newMap);
|
||||
}
|
||||
|
||||
private void handleYamlException(MarkedYAMLException e) {
|
||||
if (e.getMessage().startsWith(INVALID_CONSTRUCTOR_ERROR_INFO) || e instanceof ComposerException) {
|
||||
throw new NacosRuntimeException(NacosException.INVALID_PARAM,
|
||||
"AbstractConfigChangeListener only support basic java data type for yaml. If you want to listen "
|
||||
+ "key changes for custom classes, please use `Listener` to listener whole yaml configuration and parse it by yourself.",
|
||||
e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
private Map<String, Object> getFlattenedMap(Map<String, Object> source) {
|
||||
Map<String, Object> result = new LinkedHashMap<>(128);
|
||||
buildFlattenedMap(result, source, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, String path) {
|
||||
for (Iterator<Map.Entry<String, Object>> itr = source.entrySet().iterator(); itr.hasNext(); ) {
|
||||
Map.Entry<String, Object> e = itr.next();
|
||||
String key = String.valueOf(e.getKey());
|
||||
if (StringUtils.isNotBlank(path)) {
|
||||
if (key.startsWith("[")) {
|
||||
key = path + key;
|
||||
}
|
||||
else {
|
||||
key = path + '.' + key;
|
||||
}
|
||||
}
|
||||
if (e.getValue() instanceof String) {
|
||||
result.put(key, e.getValue());
|
||||
}
|
||||
else if (e.getValue() instanceof Map) {
|
||||
@SuppressWarnings("unchecked") Map<String, Object> map = (Map<String, Object>) e.getValue();
|
||||
buildFlattenedMap(result, map, key);
|
||||
}
|
||||
else if (e.getValue() instanceof Collection) {
|
||||
@SuppressWarnings("unchecked") Collection<Object> collection = (Collection<Object>) e.getValue();
|
||||
if (collection.isEmpty()) {
|
||||
result.put(key, "");
|
||||
}
|
||||
else {
|
||||
int count = 0;
|
||||
for (Object object : collection) {
|
||||
buildFlattenedMap(result, Collections.singletonMap("[" + (count++) + "]", object), key);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.put(key, (e.getValue() != null ? e.getValue() : ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.cloud.nacos.annotation;
|
||||
|
||||
import com.alibaba.nacos.api.config.listener.Listener;
|
||||
|
||||
public interface TargetRefreshable extends Listener {
|
||||
|
||||
Object getTarget();
|
||||
|
||||
void setTarget(Object target);
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
com.alibaba.cloud.nacos.annotation.ScaYamlConfigChangeParser
|
Loading…
Reference in New Issue