diff --git a/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/pom.xml b/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/pom.xml
index d3ba03c1..9c3ed8e7 100644
--- a/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/pom.xml
+++ b/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/pom.xml
@@ -56,6 +56,14 @@
true
+
+ io.etcd
+ jetcd-core
+ ${jetcd.version}
+ compile
+ true
+
+
org.springframework.boot
spring-boot-configuration-processor
diff --git a/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/src/main/java/cn/hippo4j/core/springboot/starter/config/BootstrapConfigProperties.java b/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/src/main/java/cn/hippo4j/core/springboot/starter/config/BootstrapConfigProperties.java
index 430786b1..62ec0ffe 100644
--- a/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/src/main/java/cn/hippo4j/core/springboot/starter/config/BootstrapConfigProperties.java
+++ b/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/src/main/java/cn/hippo4j/core/springboot/starter/config/BootstrapConfigProperties.java
@@ -17,14 +17,15 @@
package cn.hippo4j.core.springboot.starter.config;
+import java.util.List;
+import java.util.Map;
+
import cn.hippo4j.core.config.BootstrapPropertiesInterface;
import cn.hippo4j.core.springboot.starter.parser.ConfigFileTypeEnum;
import lombok.Getter;
import lombok.Setter;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import java.util.List;
-import java.util.Map;
+import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Bootstrap core properties.
@@ -86,6 +87,11 @@ public class BootstrapConfigProperties implements BootstrapPropertiesInterface {
*/
private Map zookeeper;
+ /**
+ * etcd config
+ */
+ private Map etcd;
+
/**
* Tomcat thread pool config.
*/
diff --git a/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/src/main/java/cn/hippo4j/core/springboot/starter/config/ConfigHandlerConfiguration.java b/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/src/main/java/cn/hippo4j/core/springboot/starter/config/ConfigHandlerConfiguration.java
index 23bca9d3..493d2f1d 100644
--- a/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/src/main/java/cn/hippo4j/core/springboot/starter/config/ConfigHandlerConfiguration.java
+++ b/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/src/main/java/cn/hippo4j/core/springboot/starter/config/ConfigHandlerConfiguration.java
@@ -21,8 +21,10 @@ import cn.hippo4j.core.springboot.starter.refresher.ApolloRefresherHandler;
import cn.hippo4j.core.springboot.starter.refresher.NacosCloudRefresherHandler;
import cn.hippo4j.core.springboot.starter.refresher.NacosRefresherHandler;
import cn.hippo4j.core.springboot.starter.refresher.ZookeeperRefresherHandler;
+import cn.hippo4j.core.springboot.starter.refresher.EtcdRefresherHandler;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.ConfigService;
+import io.etcd.jetcd.Client;
import lombok.RequiredArgsConstructor;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -45,6 +47,8 @@ public class ConfigHandlerConfiguration {
private static final String ZOOKEEPER_CONNECT_STR_KEY = "zookeeper.zk-connect-str";
+ private static final String ETCD = "endpoints";
+
@RequiredArgsConstructor
@ConditionalOnClass(ConfigService.class)
@ConditionalOnMissingClass(NACOS_CONFIG_MANAGER_KEY)
@@ -88,4 +92,16 @@ public class ConfigHandlerConfiguration {
return new ZookeeperRefresherHandler();
}
}
+
+ @ConditionalOnClass(Client.class)
+ @ConditionalOnProperty(prefix = BootstrapConfigProperties.PREFIX, name = ETCD)
+ static class EmbeddedEtcd {
+
+ @Bean
+ public EtcdRefresherHandler etcdRefresher() {
+ return new EtcdRefresherHandler();
+ }
+ }
+
+
}
diff --git a/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/src/main/java/cn/hippo4j/core/springboot/starter/refresher/EtcdRefresherHandler.java b/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/src/main/java/cn/hippo4j/core/springboot/starter/refresher/EtcdRefresherHandler.java
new file mode 100644
index 00000000..0754752b
--- /dev/null
+++ b/hippo4j-spring-boot/hippo4j-config-spring-boot-starter/src/main/java/cn/hippo4j/core/springboot/starter/refresher/EtcdRefresherHandler.java
@@ -0,0 +1,105 @@
+package cn.hippo4j.core.springboot.starter.refresher;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Objects;
+
+import cn.hippo4j.common.toolkit.JSONUtil;
+import cn.hippo4j.common.toolkit.StringUtil;
+import io.etcd.jetcd.ByteSequence;
+import io.etcd.jetcd.Client;
+import io.etcd.jetcd.ClientBuilder;
+import io.etcd.jetcd.KeyValue;
+import io.etcd.jetcd.Watch;
+import io.etcd.jetcd.watch.WatchEvent;
+import io.etcd.jetcd.watch.WatchResponse;
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+/**
+ *@author : wh
+ *@date : 2022/8/30 17:59
+ *@description:
+ */
+@Slf4j
+public class EtcdRefresherHandler extends AbstractCoreThreadPoolDynamicRefresh implements ApplicationContextAware {
+
+ private ApplicationContext applicationContext;
+
+ private Client client;
+
+ private static final String ENDPOINTS = "endpoints";
+
+ private static final String USER = "user";
+
+ private static final String PASSWORD = "password";
+
+ private static final String CHARSET = "charset";
+
+ private static final String AUTHORITY = "authority";
+
+ private static final String KEY = "key";
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ Map etcd = bootstrapConfigProperties.getEtcd();
+ String user = etcd.get(USER);
+ String password = etcd.get(PASSWORD);
+ String endpoints = etcd.get(ENDPOINTS);
+ String authority = etcd.get(AUTHORITY);
+ String key = etcd.get(KEY);
+ Charset charset = StringUtil.isBlank(etcd.get(CHARSET)) ? StandardCharsets.UTF_8 : Charset.forName(etcd.get(CHARSET));
+
+ ClientBuilder clientBuilder = Client.builder().endpoints(endpoints.split(","));
+
+ client = applicationContext.getBean(Client.class);
+ if (Objects.isNull(client)) {
+ client = StringUtil.isAllNotEmpty(user, password) ? clientBuilder.user(ByteSequence.from(user, charset))
+ .password(ByteSequence.from(password, charset)).authority(authority)
+ .build() : clientBuilder.build();
+ }
+
+ // todo Currently only supports json
+ KeyValue keyValue = client.getKVClient().get(ByteSequence.from(key, charset)).get().getKvs().get(0);
+ if (Objects.isNull(keyValue)) {
+ return;
+ }
+
+ client.getWatchClient().watch(ByteSequence.from(key, charset), new Watch.Listener() {
+ @Override
+ public void onNext(WatchResponse response) {
+ WatchEvent watchEvent = response.getEvents().get(0);
+ WatchEvent.EventType eventType = watchEvent.getEventType();
+ // todo Currently only supports json
+ if (Objects.equals(eventType, WatchEvent.EventType.PUT)) {
+ KeyValue keyValue1 = watchEvent.getKeyValue();
+ String value = keyValue1.getValue().toString(charset);
+ Map map = JSONUtil.parseObject(value, Map.class);
+ dynamicRefresh(keyValue1.getKey().toString(charset), map);
+ }
+
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ log.error("dynamic thread pool etcd config watcher exception ", throwable);
+ }
+
+ @Override
+ public void onCompleted() {
+ log.info("dynamic thread pool etcd config key refreshed, config key {}", key);
+ }
+ });
+
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/pom.xml b/pom.xml
index ec01bb02..ca39d771 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,6 +49,7 @@
3.4.2
2.3.2.RELEASE
1.9.1
+ 0.7.3
2.2.2
4.1.56.Final
9.0.55