You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by li...@apache.org on 2020/06/03 11:43:32 UTC

[servicecomb-java-chassis] branch master updated: [SCB-1979] load filter chains

This is an automated email from the ASF dual-hosted git repository.

liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git


The following commit(s) were added to refs/heads/master by this push:
     new 9798ca8  [SCB-1979] load filter chains
9798ca8 is described below

commit 9798ca830d57f503e8b5eac576863b0b31783727
Author: wujimin <wu...@huawei.com>
AuthorDate: Wed Jun 3 14:48:02 2020 +0800

    [SCB-1979] load filter chains
---
 .../core/filter/FilterChainsManager.java           | 146 ++++++++++++++++++++
 .../servicecomb/core/filter/FilterManager.java     | 148 +++++++++++++++++++++
 .../core/filter/config/FilterChainsConfig.java     |  78 +++++++++++
 .../core/filter/config/TransportFilterConfig.java  |  40 ++++++
 .../core/filter/config/TransportFiltersConfig.java |  54 ++++++++
 .../core/filter/FilterChainsManagerTest.java       | 103 ++++++++++++++
 .../org/apache/servicecomb/config/ConfigUtil.java  |  11 +-
 7 files changed, 579 insertions(+), 1 deletion(-)

diff --git a/core/src/main/java/org/apache/servicecomb/core/filter/FilterChainsManager.java b/core/src/main/java/org/apache/servicecomb/core/filter/FilterChainsManager.java
new file mode 100644
index 0000000..c2d1e31
--- /dev/null
+++ b/core/src/main/java/org/apache/servicecomb/core/filter/FilterChainsManager.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.servicecomb.core.filter;
+
+import static org.apache.servicecomb.foundation.common.utils.StringBuilderUtils.appendLine;
+import static org.apache.servicecomb.foundation.common.utils.StringBuilderUtils.deleteLast;
+import static org.apache.servicecomb.swagger.invocation.InvocationType.CONSUMER;
+import static org.apache.servicecomb.swagger.invocation.InvocationType.PRODUCER;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map.Entry;
+
+import org.apache.servicecomb.core.SCBEngine;
+import org.apache.servicecomb.core.filter.config.FilterChainsConfig;
+import org.apache.servicecomb.core.filter.config.TransportFiltersConfig;
+import org.apache.servicecomb.swagger.invocation.InvocationType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class FilterChainsManager {
+  private TransportFiltersConfig transportFiltersConfig;
+
+  private FilterManager filterManager;
+
+  private FilterChainsConfig consumerChainsConfig;
+
+  private FilterChainsConfig producerChainsConfig;
+
+  private boolean enabled;
+
+  @Autowired
+  public FilterChainsManager setTransportFiltersConfig(TransportFiltersConfig transportFiltersConfig) {
+    this.transportFiltersConfig = transportFiltersConfig;
+    return this;
+  }
+
+  @Value("${servicecomb.filter-chains.enabled:false}")
+  public FilterChainsManager setEnabled(boolean enabled) {
+    this.enabled = enabled;
+    return this;
+  }
+
+  public FilterManager getFilterManager() {
+    return filterManager;
+  }
+
+  @Autowired
+  public FilterChainsManager setFilterManager(FilterManager filterManager) {
+    this.filterManager = filterManager;
+    return this;
+  }
+
+  public FilterChainsManager init(SCBEngine engine) {
+    transportFiltersConfig.load();
+    filterManager.init(engine);
+
+    consumerChainsConfig = new FilterChainsConfig(transportFiltersConfig, CONSUMER);
+    producerChainsConfig = new FilterChainsConfig(transportFiltersConfig, PRODUCER);
+
+    return this;
+  }
+
+  public boolean isEnabled() {
+    return enabled;
+  }
+
+  public FilterChainsManager addProviders(FilterProvider... providers) {
+    return addProviders(Arrays.asList(providers));
+  }
+
+  public FilterChainsManager addProviders(Collection<FilterProvider> providers) {
+    filterManager.addProviders(providers);
+    return this;
+  }
+
+  public FilterNode createConsumerFilterChain(String microservice) {
+    return createFilterNode(consumerChainsConfig, microservice);
+  }
+
+  public FilterNode createProducerFilterChain(String microservice) {
+    return createFilterNode(producerChainsConfig, microservice);
+  }
+
+  public List<Filter> createConsumerFilters(String microservice) {
+    return createFilters(consumerChainsConfig, microservice);
+  }
+
+  public List<Filter> createProducerFilters(String microservice) {
+    return createFilters(producerChainsConfig, microservice);
+  }
+
+  public String collectResolvedChains() {
+    // currently not implement consumer filter chain, so not print consumer information now.
+
+    StringBuilder sb = new StringBuilder();
+    //    appendLine(sb, "consumer filters: %s", filterManager.getConsumerFilters());
+    appendLine(sb, "producer filters: %s", filterManager.getProducerFilters());
+    //    collectChainsByInvocationType(sb, consumerChainsConfig, CONSUMER);
+    collectChainsByInvocationType(sb, producerChainsConfig, PRODUCER);
+
+    return deleteLast(sb, 1).toString();
+  }
+
+  private void collectChainsByInvocationType(StringBuilder sb, FilterChainsConfig chainsConfig,
+      InvocationType invocationType) {
+    appendLine(sb, "%s chains:", invocationType.name().toLowerCase(Locale.US));
+    appendLine(sb, "  default: %s", chainsConfig.getDefaultChain());
+    for (Entry<String, List<Object>> entry : chainsConfig.getMicroserviceChains().entrySet()) {
+      appendLine(sb, "  %s: %s", entry.getKey(), entry.getValue());
+    }
+  }
+
+  private FilterNode createFilterNode(FilterChainsConfig chainsConfig, String microservice) {
+    List<Filter> filters = createFilters(chainsConfig, microservice);
+    return FilterNode.buildChain(filters);
+  }
+
+  private List<Filter> createFilters(FilterChainsConfig chainsConfig, String microservice) {
+    if (!enabled) {
+      return Collections.emptyList();
+    }
+
+    List<Object> chain = chainsConfig.findChain(microservice);
+    return filterManager.createFilters(chain);
+  }
+}
diff --git a/core/src/main/java/org/apache/servicecomb/core/filter/FilterManager.java b/core/src/main/java/org/apache/servicecomb/core/filter/FilterManager.java
new file mode 100644
index 0000000..e8846cc
--- /dev/null
+++ b/core/src/main/java/org/apache/servicecomb/core/filter/FilterManager.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.servicecomb.core.filter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+
+import org.apache.servicecomb.core.SCBEngine;
+import org.apache.servicecomb.core.filter.config.TransportFilterConfig;
+import org.apache.servicecomb.core.filter.impl.TransportFilters;
+import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils;
+import org.apache.servicecomb.swagger.invocation.InvocationType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class FilterManager {
+  interface Factory {
+    Filter create();
+  }
+
+  private SCBEngine engine;
+
+  private final List<FilterProvider> providers = new ArrayList<>(
+      SPIServiceUtils.getOrLoadSortedService(FilterProvider.class));
+
+  private final Map<String, Factory> factoryMap = new HashMap<>();
+
+  private final List<String> consumerFilters = new ArrayList<>();
+
+  private final List<String> producerFilters = new ArrayList<>();
+
+  @Autowired(required = false)
+  public void addProviders(Collection<FilterProvider> providers) {
+    this.providers.addAll(providers);
+  }
+
+  public List<String> getConsumerFilters() {
+    return consumerFilters;
+  }
+
+  public List<String> getProducerFilters() {
+    return producerFilters;
+  }
+
+  public void init(SCBEngine engine) {
+    this.engine = engine;
+    List<Class<? extends Filter>> filterClasses = providers.stream()
+        .flatMap(provider -> provider.getFilters().stream())
+        .collect(Collectors.toList());
+
+    for (Class<? extends Filter> filterClass : filterClasses) {
+      FilterMeta meta = filterClass.getAnnotation(FilterMeta.class);
+      Factory factory = buildFactory(filterClass, meta);
+
+      if (factoryMap.put(meta.name(), factory) != null) {
+        throw new IllegalStateException(
+            String.format("duplicated filter, name=%s, class=%s", meta.name(), filterClass.getName()));
+      }
+
+      if (Arrays.binarySearch(meta.invocationType(), InvocationType.CONSUMER) >= 0) {
+        consumerFilters.add(meta.name());
+      }
+      if (Arrays.binarySearch(meta.invocationType(), InvocationType.PRODUCER) >= 0) {
+        producerFilters.add(meta.name());
+      }
+    }
+  }
+
+  public List<Filter> createFilters(List<Object> chain) {
+    return chain.stream()
+        .map(filterConfig -> {
+          Filter filter = createFilter(filterConfig);
+          filter.init(engine);
+          return filter;
+        })
+        .collect(Collectors.toList());
+  }
+
+  private Filter createFilter(Object filterConfig) {
+    if (filterConfig instanceof String) {
+      return createFilterByName((String) filterConfig);
+    }
+
+    if (filterConfig instanceof TransportFilterConfig) {
+      return createTransportFilter((TransportFilterConfig) filterConfig);
+    }
+
+    throw new IllegalStateException("not support create filter by " + filterConfig);
+  }
+
+  private Filter createTransportFilter(TransportFilterConfig config) {
+    TransportFilters transportFilters = new TransportFilters();
+    for (Entry<String, List<Object>> entry : config.getFiltersByTransport().entrySet()) {
+      List<Filter> filters = createFilters(entry.getValue());
+      transportFilters.getChainByTransport().put(entry.getKey(), FilterNode.buildChain(filters));
+    }
+    return transportFilters;
+  }
+
+  private Filter createFilterByName(String filterName) {
+    Factory factory = factoryMap.get(filterName);
+    if (factory != null) {
+      return factory.create();
+    }
+
+    throw new IllegalStateException("filter not exist, name=" + filterName);
+  }
+
+  private Factory buildFactory(Class<? extends Filter> filterClass, FilterMeta meta) {
+    if (meta.shareable()) {
+      Filter filter = createFilter(filterClass);
+      return () -> filter;
+    }
+
+    return () -> createFilter(filterClass);
+  }
+
+  private Filter createFilter(Class<? extends Filter> filterClass) {
+    try {
+      Filter filter = filterClass.newInstance();
+      filter.init(engine);
+      return filter;
+    } catch (Exception e) {
+      throw new IllegalStateException("failed to create filter.", e);
+    }
+  }
+}
diff --git a/core/src/main/java/org/apache/servicecomb/core/filter/config/FilterChainsConfig.java b/core/src/main/java/org/apache/servicecomb/core/filter/config/FilterChainsConfig.java
new file mode 100644
index 0000000..a0e5156
--- /dev/null
+++ b/core/src/main/java/org/apache/servicecomb/core/filter/config/FilterChainsConfig.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.servicecomb.core.filter.config;
+
+import static org.apache.servicecomb.core.filter.config.TransportFiltersConfig.FILTER_CHAINS_PREFIX;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.servicecomb.config.ConfigUtil;
+import org.apache.servicecomb.swagger.invocation.InvocationType;
+
+import com.netflix.config.DynamicPropertyFactory;
+
+public class FilterChainsConfig {
+  private final List<Object> defaultChain;
+
+  private final Map<String, List<Object>> microserviceChains = new HashMap<>();
+
+  private final TransportFiltersConfig transportFiltersConfig;
+
+  public FilterChainsConfig(TransportFiltersConfig transportFiltersConfig, InvocationType type) {
+    this.transportFiltersConfig = transportFiltersConfig;
+
+    Configuration config = (Configuration) DynamicPropertyFactory.getBackingConfigurationSource();
+    String root = FILTER_CHAINS_PREFIX + type.name().toLowerCase(Locale.US);
+    defaultChain = resolve(ConfigUtil.getStringList(config, root + ".default"));
+    loadMicroserviceChains(config, root + ".policies");
+  }
+
+  public List<Object> getDefaultChain() {
+    return defaultChain;
+  }
+
+  public Map<String, List<Object>> getMicroserviceChains() {
+    return microserviceChains;
+  }
+
+  public List<Object> findChain(String microservice) {
+    return microserviceChains.getOrDefault(microservice, defaultChain);
+  }
+
+  private void loadMicroserviceChains(Configuration config, String policiesRoot) {
+    config.getKeys(policiesRoot).forEachRemaining(qualifiedKey -> {
+      String microserviceName = qualifiedKey.substring(policiesRoot.length() + 1);
+      List<String> chain = ConfigUtil.getStringList(config, qualifiedKey);
+
+      microserviceChains.put(microserviceName, resolve(chain));
+    });
+  }
+
+  private List<Object> resolve(List<String> rawChain) {
+    return rawChain.stream()
+        .map(value -> {
+          TransportFilterConfig config = transportFiltersConfig.getConfig(value);
+          return config == null ? value : config;
+        })
+        .collect(Collectors.toList());
+  }
+}
diff --git a/core/src/main/java/org/apache/servicecomb/core/filter/config/TransportFilterConfig.java b/core/src/main/java/org/apache/servicecomb/core/filter/config/TransportFilterConfig.java
new file mode 100644
index 0000000..e6b675d
--- /dev/null
+++ b/core/src/main/java/org/apache/servicecomb/core/filter/config/TransportFilterConfig.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.servicecomb.core.filter.config;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TransportFilterConfig {
+  private Map<String, List<Object>> filtersByTransport = new HashMap<>();
+
+  public Map<String, List<Object>> getFiltersByTransport() {
+    return filtersByTransport;
+  }
+
+  public TransportFilterConfig setTransportFilters(String transport, List<Object> filters) {
+    filtersByTransport.put(transport, filters);
+
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    return filtersByTransport.toString();
+  }
+}
diff --git a/core/src/main/java/org/apache/servicecomb/core/filter/config/TransportFiltersConfig.java b/core/src/main/java/org/apache/servicecomb/core/filter/config/TransportFiltersConfig.java
new file mode 100644
index 0000000..ef2883d
--- /dev/null
+++ b/core/src/main/java/org/apache/servicecomb/core/filter/config/TransportFiltersConfig.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.servicecomb.core.filter.config;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.configuration.Configuration;
+import org.springframework.stereotype.Component;
+
+import com.netflix.config.DynamicPropertyFactory;
+
+@Component
+public class TransportFiltersConfig {
+  public static final String FILTER_CHAINS_PREFIX = "servicecomb.filter-chains.";
+
+  public static final String ROOT = FILTER_CHAINS_PREFIX + "transport-filters";
+
+  private final Map<String, TransportFilterConfig> byName = new HashMap<>();
+
+  private final Configuration config = (Configuration) DynamicPropertyFactory.getBackingConfigurationSource();
+
+  public void load() {
+    config.getKeys(ROOT).forEachRemaining(this::loadOneChain);
+  }
+
+  private void loadOneChain(String qualifiedKey) {
+    String qualifiedName = qualifiedKey.substring(ROOT.length() + 1);
+    int dotIdx = qualifiedName.indexOf('.');
+    String name = qualifiedName.substring(0, dotIdx);
+    String transport = qualifiedName.substring(dotIdx + 1);
+
+    byName.computeIfAbsent(name, key -> new TransportFilterConfig())
+        .setTransportFilters(transport, config.getList(qualifiedKey));
+  }
+
+  public TransportFilterConfig getConfig(String name) {
+    return byName.get(name);
+  }
+}
diff --git a/core/src/test/java/org/apache/servicecomb/core/filter/FilterChainsManagerTest.java b/core/src/test/java/org/apache/servicecomb/core/filter/FilterChainsManagerTest.java
new file mode 100644
index 0000000..1a42497
--- /dev/null
+++ b/core/src/test/java/org/apache/servicecomb/core/filter/FilterChainsManagerTest.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.servicecomb.core.filter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.servicecomb.core.filter.config.TransportFiltersConfig;
+import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+public class FilterChainsManagerTest {
+  @Before
+  public void setUp() {
+    ArchaiusUtils.resetConfig();
+  }
+
+  @AfterClass
+  public static void afterAll() {
+    ArchaiusUtils.resetConfig();
+  }
+
+  private void default_chain(String filters) {
+    ArchaiusUtils.setProperty("servicecomb.filter-chains.consumer.default", filters);
+  }
+
+  private void microservice_chain(String microservice, String filters) {
+    String key = String.format("servicecomb.filter-chains.consumer.policies.%s", microservice);
+    ArchaiusUtils.setProperty(key, filters);
+  }
+
+  private FilterChainsManager createFilterChains() {
+    return new FilterChainsManager()
+        .setEnabled(true)
+        .setTransportFiltersConfig(new TransportFiltersConfig())
+        .setFilterManager(new FilterManager())
+        .addProviders(() -> Collections.singletonList(SimpleRetryFilter.class))
+        .init(null);
+  }
+
+  @Test
+  public void should_allow_not_share_filter_instance() {
+    default_chain("simple-load-balance");
+
+    FilterChainsManager filterChains = createFilterChains();
+    List<Filter> aFilters = filterChains.createConsumerFilters("a");
+    List<Filter> bFilters = filterChains.createConsumerFilters("b");
+
+    assertThat(aFilters.get(0)).isNotSameAs(bFilters.get(0));
+  }
+
+  @Test
+  public void should_allow_share_filter_instance() {
+    default_chain("simple-retry");
+
+    FilterChainsManager filterChains = createFilterChains();
+    List<Filter> aFilters = filterChains.createConsumerFilters("a");
+    List<Filter> bFilters = filterChains.createConsumerFilters("b");
+
+    assertThat(aFilters).hasSameElementsAs(bFilters);
+  }
+
+  @Test
+  public void should_allow_mix_share_and_not_share_filter_instance() {
+    default_chain("simple-load-balance, simple-retry");
+
+    FilterChainsManager filterChains = createFilterChains();
+    List<Filter> aFilters = filterChains.createConsumerFilters("a");
+    List<Filter> bFilters = filterChains.createConsumerFilters("b");
+
+    assertThat(aFilters.get(0)).isNotSameAs(bFilters.get(0));
+    assertThat(aFilters.get(1)).isSameAs(bFilters.get(1));
+  }
+
+  @Test
+  public void microservice_scope_should_override_default_scope() {
+    default_chain("simple-load-balance");
+    microservice_chain("a", "simple-retry");
+
+    FilterChainsManager filterChains = createFilterChains();
+    List<Filter> filters = filterChains.createConsumerFilters("a");
+
+    assertThat(filters.get(0)).isInstanceOf(SimpleRetryFilter.class);
+  }
+}
diff --git a/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/ConfigUtil.java b/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/ConfigUtil.java
index 6be5e8d..aed29c5 100644
--- a/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/ConfigUtil.java
+++ b/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/ConfigUtil.java
@@ -21,14 +21,17 @@ import static org.apache.servicecomb.foundation.common.base.ServiceCombConstants
 import static org.apache.servicecomb.foundation.common.base.ServiceCombConstants.CONFIG_KEY_SPLITER;
 import static org.apache.servicecomb.foundation.common.base.ServiceCombConstants.CONFIG_SERVICECOMB_PREFIX;
 
-import com.netflix.config.DynamicBooleanProperty;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
 
 import org.apache.commons.configuration.AbstractConfiguration;
 import org.apache.commons.configuration.Configuration;
@@ -98,6 +101,12 @@ public final class ConfigUtil {
     return null;
   }
 
+  public static List<String> getStringList(@Nonnull Configuration config, @Nonnull String key) {
+    return config.getList(key).stream()
+        .map(v -> Objects.toString(v, null))
+        .collect(Collectors.toList());
+  }
+
   private static void setMicroserviceConfigLoader(Configuration config, MicroserviceConfigLoader loader) {
     config.setProperty(MICROSERVICE_CONFIG_LOADER_KEY, loader);
   }