diff --git a/CHANGELOG.md b/CHANGELOG.md
index e92d54042..9f272f41d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,3 +5,4 @@
- [feat:support reading configuration from application.yml or application.properties.](https://github.com/Tencent/spring-cloud-tencent/pull/262)
- [fix:fix ClassNotFoundException while not importing openfeign when using circuit-breaker module.](https://github.com/Tencent/spring-cloud-tencent/pull/271)
- [fix:solve ratelimit-callee-service UnknownHostException.](https://github.com/Tencent/spring-cloud-tencent/pull/292)
+- [Feature: Add config change listener feature support](https://github.com/Tencent/spring-cloud-tencent/pull/299)
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java
index 79255de5f..2b61b69be 100644
--- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java
@@ -20,7 +20,9 @@ package com.tencent.cloud.polaris.config;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceAutoRefresher;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
+import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProcessor;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
+import com.tencent.cloud.polaris.config.listener.PolarisConfigChangeEventListener;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -46,4 +48,14 @@ public class PolarisConfigAutoConfiguration {
contextRefresher);
}
+ @Bean
+ public PolarisConfigAnnotationProcessor polarisConfigAnnotationProcessor() {
+ return new PolarisConfigAnnotationProcessor();
+ }
+
+ @Bean
+ public PolarisConfigChangeEventListener polarisConfigChangeEventListener() {
+ return new PolarisConfigChangeEventListener();
+ }
+
}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java
new file mode 100644
index 000000000..349799f69
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java
@@ -0,0 +1,105 @@
+/*
+ * Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
+ *
+ * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * 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.tencent.cloud.polaris.config.annotation;
+
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Sets;
+import com.tencent.cloud.polaris.config.listener.ConfigChangeEvent;
+import com.tencent.cloud.polaris.config.listener.ConfigChangeListener;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.core.Ordered;
+import org.springframework.core.PriorityOrdered;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.lang.NonNull;
+import org.springframework.util.ReflectionUtils;
+
+import static com.tencent.cloud.polaris.config.listener.PolarisConfigListenerContext.addChangeListener;
+
+/**
+ * {@link PolarisConfigAnnotationProcessor} implementation for spring .
+ *
Refer to the Apollo project implementation:
+ *
+ * ApolloAnnotationProcessor
+ * @author Palmer Xu 2022-06-07
+ */
+public class PolarisConfigAnnotationProcessor implements BeanPostProcessor, PriorityOrdered {
+
+ @Override
+ public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName)
+ throws BeansException {
+ Class> clazz = bean.getClass();
+ for (Method method : findAllMethod(clazz)) {
+ this.processPolarisConfigChangeListener(bean, method);
+ }
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
+ return bean;
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+
+ private List findAllMethod(Class> clazz) {
+ final List res = new LinkedList<>();
+ ReflectionUtils.doWithMethods(clazz, res::add);
+ return res;
+ }
+
+ private void processPolarisConfigChangeListener(final Object bean, final Method method) {
+ PolarisConfigKVFileChangeListener annotation = AnnotationUtils
+ .findAnnotation(method, PolarisConfigKVFileChangeListener.class);
+ if (annotation == null) {
+ return;
+ }
+ Class>[] parameterTypes = method.getParameterTypes();
+ Preconditions.checkArgument(parameterTypes.length == 1,
+ "Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length,
+ method);
+ Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]),
+ "Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0],
+ method);
+
+ ReflectionUtils.makeAccessible(method);
+ String[] annotatedInterestedKeys = annotation.interestedKeys();
+ String[] annotatedInterestedKeyPrefixes = annotation.interestedKeyPrefixes();
+
+ ConfigChangeListener configChangeListener = changeEvent -> ReflectionUtils.invokeMethod(method, bean, changeEvent);
+
+ Set interestedKeys =
+ annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null;
+ Set interestedKeyPrefixes =
+ annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes)
+ : null;
+
+ addChangeListener(configChangeListener, interestedKeys, interestedKeyPrefixes);
+ }
+
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java
new file mode 100644
index 000000000..6acbf2336
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java
@@ -0,0 +1,58 @@
+/*
+ * Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
+ *
+ * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * 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.tencent.cloud.polaris.config.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;
+
+/**
+ * Configuring the change listener annotation.
+ *
Refer to the Apollo project implementation:
+ *
+ * ApolloAnnotationProcessor
+ * @author Palmer Xu 2022-05-31
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface PolarisConfigKVFileChangeListener {
+
+ /**
+ * The keys interested in the listener, will only be notified if any of the interested keys is changed.
+ *
+ * If neither of {@code interestedKeys} and {@code interestedKeyPrefixes} is specified then the {@code listener} will be notified when any key is changed.
+ * @return interested keys in the listener
+ */
+ String[] interestedKeys() default {};
+
+ /**
+ * The key prefixes that the listener is interested in, will be notified if and only if the changed keys start with anyone of the prefixes.
+ * The prefixes will simply be used to determine whether the {@code listener} should be notified or not using {@code changedKey.startsWith(prefix)}.
+ * e.g. "spring." means that {@code listener} is interested in keys that starts with "spring.", such as "spring.banner", "spring.jpa", etc.
+ * and "application" means that {@code listener} is interested in keys that starts with "application", such as "applicationName", "application.port", etc.
+ *
+ * If neither of {@code interestedKeys} and {@code interestedKeyPrefixes} is specified then the {@code listener} will be notified when whatever key is changed.
+ * @return interested key-prefixed in the listener
+ */
+ String[] interestedKeyPrefixes() default {};
+
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/ConfigChangeEvent.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/ConfigChangeEvent.java
new file mode 100644
index 000000000..119a5ce4c
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/ConfigChangeEvent.java
@@ -0,0 +1,88 @@
+/*
+ * Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
+ *
+ * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * 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.tencent.cloud.polaris.config.listener;
+
+import java.util.Map;
+import java.util.Set;
+
+import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo;
+
+/**
+ * A change event when config is changed .
+ *
+ * @author Palmer Xu 2022-06-07
+ */
+public final class ConfigChangeEvent {
+
+ /**
+ * all changes keys map.
+ */
+ private final Map changes;
+
+ /**
+ * all interested changed keys.
+ */
+ private final Set interestedChangedKeys;
+
+ /**
+ * Config Change Event Constructor.
+ * @param changes all changes keys map
+ * @param interestedChangedKeys all interested changed keys
+ */
+ public ConfigChangeEvent(Map changes, Set interestedChangedKeys) {
+ this.changes = changes;
+ this.interestedChangedKeys = interestedChangedKeys;
+ }
+
+ /**
+ * Get the keys changed.
+ * @return the list of the keys
+ */
+ public Set changedKeys() {
+ return changes.keySet();
+ }
+
+ /**
+ * Get a specific change instance for the key specified.
+ * @param key the changed key
+ * @return the change instance
+ */
+ public ConfigPropertyChangeInfo getChange(String key) {
+ return changes.get(key);
+ }
+
+ /**
+ * Check whether the specified key is changed .
+ * @param key the key
+ * @return true if the key is changed, false otherwise.
+ */
+ public boolean isChanged(String key) {
+ return changes.containsKey(key);
+ }
+
+ /**
+ * Maybe subclass override this method.
+ *
+ * @return interested and changed keys
+ */
+ public Set interestedChangedKeys() {
+ return interestedChangedKeys;
+ }
+
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/ConfigChangeListener.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/ConfigChangeListener.java
new file mode 100644
index 000000000..14ab64a32
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/ConfigChangeListener.java
@@ -0,0 +1,35 @@
+/*
+ * Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
+ *
+ * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * 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.tencent.cloud.polaris.config.listener;
+
+/**
+ * Configuring the change listener interface.
+ *
+ * @author Palmer Xu 2022-05-31
+ */
+public interface ConfigChangeListener {
+
+ /**
+ * Invoked when there is any config change for the namespace.
+ *
+ * @param changeEvent the event for this change
+ */
+ void onChange(ConfigChangeEvent changeEvent);
+
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigChangeEventListener.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigChangeEventListener.java
new file mode 100644
index 000000000..0a55255d5
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigChangeEventListener.java
@@ -0,0 +1,111 @@
+/*
+ * Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
+ *
+ * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * 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.tencent.cloud.polaris.config.listener;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.google.common.collect.Maps;
+import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo;
+
+import org.springframework.boot.context.event.ApplicationStartedEvent;
+import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.Environment;
+import org.springframework.core.env.MutablePropertySources;
+import org.springframework.lang.NonNull;
+
+import static com.tencent.cloud.polaris.config.listener.PolarisConfigListenerContext.fireConfigChange;
+import static com.tencent.cloud.polaris.config.listener.PolarisConfigListenerContext.initialize;
+import static com.tencent.cloud.polaris.config.listener.PolarisConfigListenerContext.merge;
+
+/**
+ * Polaris Config Change Event Listener .
+ *
+ * @author Elve.Xu 2022-06-08
+ */
+public final class PolarisConfigChangeEventListener implements ApplicationListener {
+
+ private static final AtomicBoolean started = new AtomicBoolean();
+
+ /**
+ * Handle an application event.
+ *
+ * @param event the event to respond to
+ */
+ @Override
+ public void onApplicationEvent(@NonNull ApplicationEvent event) {
+
+ // Initialize application all environment properties .
+ if (event instanceof ApplicationStartedEvent && started.compareAndSet(false, true)) {
+ ApplicationStartedEvent applicationStartedEvent = (ApplicationStartedEvent) event;
+ ConfigurableEnvironment environment = applicationStartedEvent.getApplicationContext().getEnvironment();
+ Map ret = loadEnvironmentProperties(environment);
+ if (!ret.isEmpty()) {
+ initialize(ret);
+ }
+ }
+
+ // Process Environment Change Event .
+ if (event instanceof EnvironmentChangeEvent) {
+ EnvironmentChangeEvent environmentChangeEvent = (EnvironmentChangeEvent) event;
+ ConfigurableApplicationContext context = (ConfigurableApplicationContext) environmentChangeEvent.getSource();
+ ConfigurableEnvironment environment = context.getEnvironment();
+ Map ret = loadEnvironmentProperties(environment);
+ Map changes = merge(ret);
+ fireConfigChange(changes.keySet(), Maps.newHashMap(changes));
+ changes.clear();
+ }
+ }
+
+ /**
+ * Try load all application environment config properties .
+ * @param environment application environment instance of {@link Environment}
+ * @return properties
+ */
+ @SuppressWarnings("unchecked")
+ private Map loadEnvironmentProperties(ConfigurableEnvironment environment) {
+ Map ret = Maps.newHashMap();
+ MutablePropertySources sources = environment.getPropertySources();
+ sources.iterator().forEachRemaining(propertySource -> {
+ Object o = propertySource.getSource();
+ if (o instanceof Map) {
+ for (Map.Entry entry : ((Map) o).entrySet()) {
+ String key = entry.getKey();
+ String value = environment.getProperty(key);
+ ret.put(key, value);
+ }
+ }
+ else if (o instanceof Collection) {
+ int count = 0;
+ Collection