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