diff --git a/README.md b/README.md index 078ef506517b5be2902e2eb6a3280760e178da1a..69f424c0515d7d5135d53a8c271979a6016bfce2 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,13 @@ Apollo(配置中心) [![Build Status](https://travis-ci.org/ctripcorp/apollo.svg?branch=master)](https://travis-ci.org/ctripcorp/apollo) [![GitHub release](https://img.shields.io/github/release/ctripcorp/apollo.svg)](https://github.com/ctripcorp/apollo/releases) +![maven](https://img.shields.io/maven-central/v/com.ctrip.framework.apollo/apollo.svg) [![Coverage Status](https://coveralls.io/repos/github/ctripcorp/apollo/badge.svg?branch=master)](https://coveralls.io/github/ctripcorp/apollo?branch=master) -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) Coverity Scan Build Status [![codecov.io](https://codecov.io/github/ctripcorp/apollo/coverage.svg?branch=master)](https://codecov.io/github/ctripcorp/apollo?branch=master) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。 @@ -199,3 +200,4 @@ The project is licensed under the [Apache 2 license](https://github.com/ctripcor ![河姆渡](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/homedo.png) ![新网银行](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/xwbank.png) ![中旅安信云贷](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/ctspcl.png) +![美柚](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/meiyou.png) diff --git a/apollo-adminservice/pom.xml b/apollo-adminservice/pom.xml index ac2ddfd932ae9775ee917164c0f9a2d424946184..5ff36d4552db03e90e8c854646e537e2b7508aa1 100644 --- a/apollo-adminservice/pom.xml +++ b/apollo-adminservice/pom.xml @@ -4,7 +4,7 @@ com.ctrip.framework.apollo apollo - 0.11.0 + 1.0.0 ../pom.xml 4.0.0 diff --git a/apollo-adminservice/src/main/docker/Dockerfile b/apollo-adminservice/src/main/docker/Dockerfile index 0706bed3c80bc8fe33e833aa5978e7e818c99426..4c27c7bfb2bef32395fc0374fcf4f258c7148281 100755 --- a/apollo-adminservice/src/main/docker/Dockerfile +++ b/apollo-adminservice/src/main/docker/Dockerfile @@ -7,7 +7,7 @@ FROM openjdk:8-jre-alpine MAINTAINER ameizi -ENV VERSION 0.11.0 +ENV VERSION 1.0.0 RUN echo "http://mirrors.aliyun.com/alpine/v3.6/main" > /etc/apk/repositories \ && echo "http://mirrors.aliyun.com/alpine/v3.6/community" >> /etc/apk/repositories \ diff --git a/apollo-assembly/pom.xml b/apollo-assembly/pom.xml index 733dccaf7f7863b9b6691e6d8d9b2065a9d09daf..cf102ed5b70ee906177a79d554b8c3c642e662cc 100644 --- a/apollo-assembly/pom.xml +++ b/apollo-assembly/pom.xml @@ -4,7 +4,7 @@ com.ctrip.framework.apollo apollo - 0.11.0 + 1.0.0 ../pom.xml 4.0.0 diff --git a/apollo-biz/pom.xml b/apollo-biz/pom.xml index c6cfce695e2d63bfb734039d394f5da34385bdc0..25c83fe3b33b16ec0249f49b841e41dd4f29e550 100644 --- a/apollo-biz/pom.xml +++ b/apollo-biz/pom.xml @@ -4,7 +4,7 @@ com.ctrip.framework.apollo apollo - 0.11.0 + 1.0.0 4.0.0 apollo-biz diff --git a/apollo-buildtools/pom.xml b/apollo-buildtools/pom.xml index b888ec69997bc3ec0fb5a92916478d45fba6bbfa..984233e8f586712269a6d20da98566310a1f0720 100644 --- a/apollo-buildtools/pom.xml +++ b/apollo-buildtools/pom.xml @@ -4,7 +4,7 @@ com.ctrip.framework.apollo apollo - 0.11.0 + 1.0.0 ../pom.xml 4.0.0 diff --git a/apollo-client/README.md b/apollo-client/README.md index fd4686d85d7dc370b3c7d4e0399db0466cf87402..afcf7eea1317133523e5f9fdcc494488a70d37f5 100644 --- a/apollo-client/README.md +++ b/apollo-client/README.md @@ -88,7 +88,7 @@ If you need this functionality, you could specify the cluster as follows: com.ctrip.framework.apollo apollo-client - 0.11.0 + 1.0.0 ## III. Client Usage diff --git a/apollo-client/pom.xml b/apollo-client/pom.xml index fe35958db4c16556ec5615a1841b076d7c0629bb..87df278a297ead3a1b2c43b2e60fa9aa74db1291 100644 --- a/apollo-client/pom.xml +++ b/apollo-client/pom.xml @@ -4,7 +4,7 @@ com.ctrip.framework.apollo apollo - 0.11.0 + 1.0.0 ../pom.xml 4.0.0 diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/Config.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/Config.java index 7eaff8eacf04d3e2c8545ab7c944c0f0140819fe..ec779d430d032763730b2c8001190a26c3d74c47 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/Config.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/Config.java @@ -162,12 +162,20 @@ public interface Config { public long getDurationProperty(String key, long defaultValue); /** - * Add change listener to this config instance. + * Add change listener to this config instance, will be notified when any key is changed in this namespace. * * @param listener the config change listener */ public void addChangeListener(ConfigChangeListener listener); + /** + * Add change listener to this config instance, will only be notified when any of the interested keys is changed in this namespace. + * + * @param listener the config change listener + * @param interestedKeys the keys interested by the listener + */ + public void addChangeListener(ConfigChangeListener listener, Set interestedKeys); + /** * Return a set of the property names * diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfig.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfig.java index 965301c94082ee969d56298123fcae1e0c4f933b..a1a333288c43eab08f612d0affd72443938d08dd 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfig.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfig.java @@ -40,10 +40,11 @@ import com.google.common.collect.Sets; public abstract class AbstractConfig implements Config { private static final Logger logger = LoggerFactory.getLogger(AbstractConfig.class); - private static ExecutorService m_executorService; + private static final ExecutorService m_executorService; - private List m_listeners = Lists.newCopyOnWriteArrayList(); - private ConfigUtil m_configUtil; + private final List m_listeners = Lists.newCopyOnWriteArrayList(); + private final Map> m_interestedKeys = Maps.newConcurrentMap(); + private final ConfigUtil m_configUtil; private volatile Cache m_integerCache; private volatile Cache m_longCache; private volatile Cache m_shortCache; @@ -53,9 +54,9 @@ public abstract class AbstractConfig implements Config { private volatile Cache m_booleanCache; private volatile Cache m_dateCache; private volatile Cache m_durationCache; - private Map> m_arrayCache; - private List allCaches; - private AtomicLong m_configVersion; //indicate config version + private final Map> m_arrayCache; + private final List allCaches; + private final AtomicLong m_configVersion; //indicate config version static { m_executorService = Executors.newCachedThreadPool(ApolloThreadFactory @@ -71,8 +72,16 @@ public abstract class AbstractConfig implements Config { @Override public void addChangeListener(ConfigChangeListener listener) { + addChangeListener(listener, null); + } + + @Override + public void addChangeListener(ConfigChangeListener listener, Set interestedKeys) { if (!m_listeners.contains(listener)) { m_listeners.add(listener); + if (interestedKeys != null && !interestedKeys.isEmpty()) { + m_interestedKeys.put(listener, Sets.newHashSet(interestedKeys)); + } } } @@ -395,6 +404,10 @@ public abstract class AbstractConfig implements Config { protected void fireConfigChange(final ConfigChangeEvent changeEvent) { for (final ConfigChangeListener listener : m_listeners) { + // check whether the listener is interested in this change event + if (!isConfigChangeListenerInterested(listener, changeEvent)) { + continue; + } m_executorService.submit(new Runnable() { @Override public void run() { @@ -415,6 +428,22 @@ public abstract class AbstractConfig implements Config { } } + private boolean isConfigChangeListenerInterested(ConfigChangeListener configChangeListener, ConfigChangeEvent configChangeEvent) { + Set interestedKeys = m_interestedKeys.get(configChangeListener); + + if (interestedKeys == null || interestedKeys.isEmpty()) { + return true; // no interested keys means interested in all keys + } + + for (String interestedKey : interestedKeys) { + if (configChangeEvent.isChanged(interestedKey)) { + return true; + } + } + + return false; + } + List calcPropertyChanges(String namespace, Properties previous, Properties current) { if (previous == null) { diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultMetaServerProvider.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultMetaServerProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..3c2bd8daf629d821fc183929e132b3db4281c640 --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultMetaServerProvider.java @@ -0,0 +1,58 @@ +package com.ctrip.framework.apollo.internals; + +import com.ctrip.framework.apollo.core.ConfigConsts; +import com.ctrip.framework.apollo.core.enums.Env; +import com.ctrip.framework.apollo.core.spi.MetaServerProvider; +import com.ctrip.framework.foundation.Foundation; +import com.google.common.base.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultMetaServerProvider implements MetaServerProvider { + + public static final int ORDER = 0; + private static final Logger logger = LoggerFactory.getLogger(DefaultMetaServerProvider.class); + + private final String metaServerAddress; + + public DefaultMetaServerProvider() { + metaServerAddress = initMetaServerAddress(); + } + + private String initMetaServerAddress() { + // 1. Get from System Property + String metaAddress = System.getProperty(ConfigConsts.APOLLO_META_KEY); + if (Strings.isNullOrEmpty(metaAddress)) { + // 2. Get from OS environment variable, which could not contain dot and is normally in UPPER case + metaAddress = System.getenv("APOLLO_META"); + } + if (Strings.isNullOrEmpty(metaAddress)) { + // 3. Get from server.properties + metaAddress = Foundation.server().getProperty(ConfigConsts.APOLLO_META_KEY, null); + } + if (Strings.isNullOrEmpty(metaAddress)) { + // 4. Get from app.properties + metaAddress = Foundation.app().getProperty(ConfigConsts.APOLLO_META_KEY, null); + } + + if (Strings.isNullOrEmpty(metaAddress)) { + logger.warn("Could not find meta server address, because it is not available in neither (1) JVM system property 'apollo.meta', (2) OS env variable 'APOLLO_META' (3) property 'apollo.meta' from server.properties nor (4) property 'apollo.meta' from app.properties"); + } else { + metaAddress = metaAddress.trim(); + logger.info("Located meta services from apollo.meta configuration: {}!", metaAddress); + } + + return metaAddress; + } + + @Override + public String getMetaServerAddress(Env targetEnv) { + //for default meta server provider, we don't care the actual environment + return metaServerAddress; + } + + @Override + public int getOrder() { + return ORDER; + } +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java index 9ceddc0820f0ac584ba62a251c8ff88cac007cc9..d8a8cd332066acd53f04b00e5a9450fb45840635 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java @@ -5,8 +5,10 @@ import com.ctrip.framework.apollo.ConfigChangeListener; import com.ctrip.framework.apollo.ConfigService; import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.Set; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.ReflectionUtils; @@ -51,6 +53,8 @@ public class ApolloAnnotationProcessor extends ApolloProcessor { ReflectionUtils.makeAccessible(method); String[] namespaces = annotation.value(); + String[] annotatedInterestedKeys = annotation.interestedKeys(); + Set interestedKeys = annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null; ConfigChangeListener configChangeListener = new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { @@ -61,7 +65,11 @@ public class ApolloAnnotationProcessor extends ApolloProcessor { for (String namespace : namespaces) { Config config = ConfigService.getConfig(namespace); - config.addChangeListener(configChangeListener); + if (interestedKeys == null) { + config.addChangeListener(configChangeListener); + } else { + config.addChangeListener(configChangeListener, interestedKeys); + } } } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigChangeListener.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigChangeListener.java index 02e2a94783c0e6e7d4e671297bde4e7058852dcc..972672f85206567004c457d03497d2aa7bcc16cc 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigChangeListener.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigChangeListener.java @@ -13,11 +13,17 @@ import com.ctrip.framework.apollo.core.ConfigConsts; * *

Usage example:

*
- * //Listener on namespaces of "someNamespace" and "anotherNamespace"
+ * //Listener on namespaces of "someNamespace" and "anotherNamespace", will be notified when any key is changed
  * @ApolloConfigChangeListener({"someNamespace","anotherNamespace"})
  * private void onChange(ConfigChangeEvent changeEvent) {
  *     //handle change event
  * }
+ * 
+ * //Listener on namespaces of "someNamespace" and "anotherNamespace", will only be notified when "someKey" or "anotherKey" is changed + * @ApolloConfigChangeListener(value = {"someNamespace","anotherNamespace"}, interestedKeys = {"someKey", "anotherKey"}) + * private void onChange(ConfigChangeEvent changeEvent) { + * //handle change event + * } *
* * @author Jason Song(song_s@ctrip.com) @@ -30,4 +36,11 @@ public @interface ApolloConfigChangeListener { * Apollo namespace for the config, if not specified then default to application */ String[] value() default {ConfigConsts.NAMESPACE_APPLICATION}; + + /** + * The keys interested by the listener, will only be notified if any of the interested keys is changed. + *
+ * If not specified then will be notified when any key is changed. + */ + String[] interestedKeys() default {}; } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java index 3efb59772f6851f16189e543ff5e6fab76d87325..908792b30d98110ac8ea46095631b15b197a51fc 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java @@ -7,6 +7,7 @@ import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory; import com.ctrip.framework.apollo.spring.config.PropertySourcesConstants; import com.ctrip.framework.apollo.spring.util.SpringInjector; import com.google.common.base.Splitter; +import com.google.common.base.Strings; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,17 +17,22 @@ import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.ConfigurableEnvironment; /** - * Inject the Apollo config in Spring Boot bootstrap phase + * Initialize apollo system properties and inject the Apollo config in Spring Boot bootstrap phase * *

Configuration example:

*
- *   # will inject 'application' namespace in bootstrap phase
+ *   # set app.id
+ *   app.id = 100004458
+ *   # enable apollo bootstrap config and inject 'application' namespace in bootstrap phase
  *   apollo.bootstrap.enabled = true
  * 
* * or * *
+ *   # set app.id
+ *   app.id = 100004458
+ *   # enable apollo bootstrap config
  *   apollo.bootstrap.enabled = true
  *   # will inject 'application' and 'FX.apollo' namespaces in bootstrap phase
  *   apollo.bootstrap.namespaces = application,FX.apollo
@@ -36,6 +42,8 @@ public class ApolloApplicationContextInitializer implements
     ApplicationContextInitializer {
   private static final Logger logger = LoggerFactory.getLogger(ApolloApplicationContextInitializer.class);
   private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
+  private static final String[] APOLLO_SYSTEM_PROPERTIES = {"app.id", ConfigConsts.APOLLO_CLUSTER_KEY,
+      "apollo.cacheDir", ConfigConsts.APOLLO_META_KEY};
 
   private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector
       .getInstance(ConfigPropertySourceFactory.class);
@@ -43,6 +51,9 @@ public class ApolloApplicationContextInitializer implements
   @Override
   public void initialize(ConfigurableApplicationContext context) {
     ConfigurableEnvironment environment = context.getEnvironment();
+
+    initializeSystemProperty(environment);
+
     String enabled = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, "false");
     if (!Boolean.valueOf(enabled)) {
       logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${{}}", context, PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED);
@@ -68,4 +79,27 @@ public class ApolloApplicationContextInitializer implements
 
     environment.getPropertySources().addFirst(composite);
   }
+
+  /**
+   * To fill system properties from environment config
+   */
+  void initializeSystemProperty(ConfigurableEnvironment environment) {
+    for (String propertyName : APOLLO_SYSTEM_PROPERTIES) {
+      fillSystemPropertyFromEnvironment(environment, propertyName);
+    }
+  }
+
+  private void fillSystemPropertyFromEnvironment(ConfigurableEnvironment environment, String propertyName) {
+    if (System.getProperty(propertyName) != null) {
+      return;
+    }
+
+    String propertyValue = environment.getProperty(propertyName);
+
+    if (Strings.isNullOrEmpty(propertyValue)) {
+      return;
+    }
+
+    System.setProperty(propertyName, propertyValue);
+  }
 }
diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java
index 96663f8e2297c7ee91ed5e6e150afd9799f9780f..174c354e2ae520d286de9121948bbcd1229789eb 100644
--- a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java
+++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java
@@ -10,7 +10,6 @@ import com.ctrip.framework.apollo.core.ConfigConsts;
 import com.ctrip.framework.apollo.core.MetaDomainConsts;
 import com.ctrip.framework.apollo.core.enums.Env;
 import com.ctrip.framework.apollo.core.enums.EnvUtils;
-import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
 import com.ctrip.framework.foundation.Foundation;
 import com.google.common.base.Strings;
 
@@ -98,19 +97,10 @@ public class ConfigUtil {
   /**
    * Get the current environment.
    *
-   * @return the env
-   * @throws ApolloConfigException if env is set
+   * @return the env, UNKNOWN if env is not set or invalid
    */
   public Env getApolloEnv() {
-    Env env = EnvUtils.transformEnv(Foundation.server().getEnvType());
-    if (env == null) {
-      String path = isOSWindows() ? "C:\\opt\\settings\\server.properties" :
-          "/opt/settings/server.properties";
-      String message = String.format("env is not set, please make sure it is set in %s!", path);
-      logger.error(message);
-      throw new ApolloConfigException(message);
-    }
-    return env;
+    return EnvUtils.transformEnv(Foundation.server().getEnvType());
   }
 
   public String getLocalIp() {
@@ -234,8 +224,7 @@ public class ConfigUtil {
 
   public boolean isInLocalMode() {
     try {
-      Env env = getApolloEnv();
-      return env == Env.LOCAL;
+      return Env.LOCAL == getApolloEnv();
     } catch (Throwable ex) {
       //ignore
     }
diff --git a/apollo-client/src/main/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider b/apollo-client/src/main/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider
new file mode 100644
index 0000000000000000000000000000000000000000..35db9bfc4b5fc4197f169cd839f640e1154fbe67
--- /dev/null
+++ b/apollo-client/src/main/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider
@@ -0,0 +1 @@
+com.ctrip.framework.apollo.internals.DefaultMetaServerProvider
diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/DefaultConfigTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/DefaultConfigTest.java
index 19b7ab75ca3da1e3860407b6a958e343b2343619..d86f092e30a23e2cc3246950b291b833c1f0db60 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/DefaultConfigTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/DefaultConfigTest.java
@@ -2,14 +2,18 @@ package com.ctrip.framework.apollo.internals;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import java.io.File;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.Collections;
@@ -647,6 +651,56 @@ public class DefaultConfigTest {
     assertEquals(PropertyChangeType.ADDED, newKeyChange.getChangeType());
   }
 
+  @Test
+  public void testFireConfigChangeWithInterestedKeys() throws Exception {
+    String someKeyChanged = "someKeyChanged";
+    String anotherKeyChanged = "anotherKeyChanged";
+    String someKeyNotChanged = "someKeyNotChanged";
+    String someNamespace = "someNamespace";
+    Map changes = Maps.newHashMap();
+    changes.put(someKeyChanged, mock(ConfigChange.class));
+    changes.put(anotherKeyChanged, mock(ConfigChange.class));
+    ConfigChangeEvent someChangeEvent = new ConfigChangeEvent(someNamespace, changes);
+
+    final SettableFuture interestedInAllKeysFuture = SettableFuture.create();
+    ConfigChangeListener interestedInAllKeys = new ConfigChangeListener() {
+      @Override
+      public void onChange(ConfigChangeEvent changeEvent) {
+        interestedInAllKeysFuture.set(changeEvent);
+      }
+    };
+
+    final SettableFuture interestedInSomeKeyFuture = SettableFuture.create();
+    ConfigChangeListener interestedInSomeKey = new ConfigChangeListener() {
+      @Override
+      public void onChange(ConfigChangeEvent changeEvent) {
+        interestedInSomeKeyFuture.set(changeEvent);
+      }
+    };
+
+    final SettableFuture interestedInSomeKeyNotChangedFuture = SettableFuture.create();
+    ConfigChangeListener interestedInSomeKeyNotChanged = new ConfigChangeListener() {
+      @Override
+      public void onChange(ConfigChangeEvent changeEvent) {
+        interestedInSomeKeyNotChangedFuture.set(changeEvent);
+      }
+    };
+
+    DefaultConfig config = new DefaultConfig(someNamespace, mock(ConfigRepository.class));
+
+    config.addChangeListener(interestedInAllKeys);
+    config.addChangeListener(interestedInSomeKey, Sets.newHashSet(someKeyChanged));
+    config.addChangeListener(interestedInSomeKeyNotChanged, Sets.newHashSet(someKeyNotChanged));
+
+    config.fireConfigChange(someChangeEvent);
+
+    ConfigChangeEvent changeEvent = interestedInAllKeysFuture.get(500, TimeUnit.MILLISECONDS);
+
+    assertEquals(someChangeEvent, changeEvent);
+    assertEquals(someChangeEvent, interestedInSomeKeyFuture.get(500, TimeUnit.MILLISECONDS));
+    assertFalse(interestedInSomeKeyNotChangedFuture.isDone());
+  }
+
   @Test
   public void testGetPropertyNames() {
     String someKeyPrefix = "someKey";
diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/DefaultMetaServerProviderTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/DefaultMetaServerProviderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f84075f6d1922b2d7f16cc6a8dc2217c58ddf811
--- /dev/null
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/DefaultMetaServerProviderTest.java
@@ -0,0 +1,38 @@
+package com.ctrip.framework.apollo.internals;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.ctrip.framework.apollo.core.ConfigConsts;
+import com.ctrip.framework.apollo.core.enums.Env;
+import org.junit.After;
+import org.junit.Test;
+
+public class DefaultMetaServerProviderTest {
+
+  @After
+  public void tearDown() throws Exception {
+    System.clearProperty(ConfigConsts.APOLLO_META_KEY);
+  }
+
+  @Test
+  public void testWithSystemProperty() throws Exception {
+    String someMetaAddress = "someMetaAddress";
+    Env someEnv = Env.DEV;
+
+    System.setProperty(ConfigConsts.APOLLO_META_KEY, " " + someMetaAddress + " ");
+
+    DefaultMetaServerProvider defaultMetaServerProvider = new DefaultMetaServerProvider();
+
+    assertEquals(someMetaAddress, defaultMetaServerProvider.getMetaServerAddress(someEnv));
+  }
+
+  @Test
+  public void testWithNoSystemProperty() throws Exception {
+    Env someEnv = Env.DEV;
+
+    DefaultMetaServerProvider defaultMetaServerProvider = new DefaultMetaServerProvider();
+
+    assertNull(defaultMetaServerProvider.getMetaServerAddress(someEnv));
+  }
+}
diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index c278eb92edc4a33ab51ca57e6cbc45daa4e39ecb..09c452ad88e66a3476f5fbcd1942a05466d5190c 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -1,19 +1,12 @@
 package com.ctrip.framework.apollo.spring;
 
+import static java.util.Arrays.asList;
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
-
-import java.util.List;
-
-import org.junit.Test;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.springframework.beans.factory.BeanCreationException;
-import org.springframework.context.annotation.AnnotationConfigApplicationContext;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import com.ctrip.framework.apollo.Config;
 import com.ctrip.framework.apollo.ConfigChangeListener;
@@ -23,6 +16,17 @@ import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
 import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
 import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
 
 /**
  * @author Jason Song(song_s@ctrip.com)
@@ -200,6 +204,39 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest {
     assertEquals(someEvent, bean.getSomeChangeEvent());
   }
 
+  @Test
+  public void testApolloConfigChangeListenerWithInterestedKeys() throws Exception {
+    Config applicationConfig = mock(Config.class);
+    Config fxApolloConfig = mock(Config.class);
+
+    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
+    mockConfig(FX_APOLLO_NAMESPACE, fxApolloConfig);
+
+    TestApolloConfigChangeListenerWithInterestedKeysBean bean = getBean(
+        TestApolloConfigChangeListenerWithInterestedKeysBean.class, AppConfig8.class);
+
+    final ArgumentCaptor applicationConfigInterestedKeys = ArgumentCaptor.forClass(Set.class);
+    final ArgumentCaptor fxApolloConfigInterestedKeys = ArgumentCaptor.forClass(Set.class);
+
+    verify(applicationConfig, times(2))
+        .addChangeListener(any(ConfigChangeListener.class), applicationConfigInterestedKeys.capture());
+
+    verify(fxApolloConfig, times(1))
+        .addChangeListener(any(ConfigChangeListener.class), fxApolloConfigInterestedKeys.capture());
+
+    assertEquals(2, applicationConfigInterestedKeys.getAllValues().size());
+
+    Set result = Sets.newHashSet();
+    for (Set interestedKeys : applicationConfigInterestedKeys.getAllValues()) {
+      result.addAll(interestedKeys);
+    }
+    assertEquals(Sets.newHashSet("someKey", "anotherKey"), result);
+
+    assertEquals(1, fxApolloConfigInterestedKeys.getAllValues().size());
+
+    assertEquals(asList(Sets.newHashSet("anotherKey")), fxApolloConfigInterestedKeys.getAllValues());
+  }
+
   private  T getBean(Class beanClass, Class... annotatedClasses) {
     AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses);
 
@@ -269,6 +306,15 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest {
     }
   }
 
+  @Configuration
+  @EnableApolloConfig
+  static class AppConfig8 {
+    @Bean
+    public TestApolloConfigChangeListenerWithInterestedKeysBean bean() {
+      return new TestApolloConfigChangeListenerWithInterestedKeysBean();
+    }
+  }
+
   static class TestApolloConfigBean1 {
     @ApolloConfig
     private Config config;
@@ -365,4 +411,16 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest {
       return someChangeEvent;
     }
   }
+
+  static class TestApolloConfigChangeListenerWithInterestedKeysBean {
+
+    @ApolloConfigChangeListener(interestedKeys = {"someKey"})
+    private void someOnChange(ConfigChangeEvent changeEvent) {}
+
+    @ApolloConfigChangeListener(value = {ConfigConsts.NAMESPACE_APPLICATION, FX_APOLLO_NAMESPACE},
+        interestedKeys = {"anotherKey"})
+    private void anotherOnChange(ConfigChangeEvent changeEvent) {
+
+    }
+  }
 }
diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XMLConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XMLConfigAnnotationTest.java
index a58c0a8de35615d9d8712c34eeb07465424bfa77..0010a00dee846a2a596a0e3689d157f66d01d0ba 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XMLConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XMLConfigAnnotationTest.java
@@ -1,13 +1,19 @@
 package com.ctrip.framework.apollo.spring;
 
+import static java.util.Arrays.asList;
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
+import com.google.common.collect.Sets;
 import java.util.List;
 
+import java.util.Set;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 import org.springframework.beans.factory.BeanCreationException;
@@ -125,6 +131,39 @@ public class XMLConfigAnnotationTest extends AbstractSpringIntegrationTest {
     getBean("spring/XmlConfigAnnotationTest5.xml", TestApolloConfigChangeListenerBean3.class);
   }
 
+  @Test
+  public void testApolloConfigChangeListenerWithInterestedKeys() throws Exception {
+    Config applicationConfig = mock(Config.class);
+    Config fxApolloConfig = mock(Config.class);
+
+    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
+    mockConfig(FX_APOLLO_NAMESPACE, fxApolloConfig);
+
+    TestApolloConfigChangeListenerWithInterestedKeysBean bean = getBean(
+        "spring/XmlConfigAnnotationTest6.xml", TestApolloConfigChangeListenerWithInterestedKeysBean.class);
+
+    final ArgumentCaptor applicationConfigInterestedKeys = ArgumentCaptor.forClass(Set.class);
+    final ArgumentCaptor fxApolloConfigInterestedKeys = ArgumentCaptor.forClass(Set.class);
+
+    verify(applicationConfig, times(2))
+        .addChangeListener(any(ConfigChangeListener.class), applicationConfigInterestedKeys.capture());
+
+    verify(fxApolloConfig, times(1))
+        .addChangeListener(any(ConfigChangeListener.class), fxApolloConfigInterestedKeys.capture());
+
+    assertEquals(2, applicationConfigInterestedKeys.getAllValues().size());
+
+    Set result = Sets.newHashSet();
+    for (Set interestedKeys : applicationConfigInterestedKeys.getAllValues()) {
+      result.addAll(interestedKeys);
+    }
+    assertEquals(Sets.newHashSet("someKey", "anotherKey"), result);
+
+    assertEquals(1, fxApolloConfigInterestedKeys.getAllValues().size());
+
+    assertEquals(asList(Sets.newHashSet("anotherKey")), fxApolloConfigInterestedKeys.getAllValues());
+  }
+
   private  T getBean(String xmlLocation, Class beanClass) {
     ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlLocation);
 
@@ -204,4 +243,15 @@ public class XMLConfigAnnotationTest extends AbstractSpringIntegrationTest {
     }
   }
 
+  static class TestApolloConfigChangeListenerWithInterestedKeysBean {
+
+    @ApolloConfigChangeListener(interestedKeys = {"someKey"})
+    private void someOnChange(ConfigChangeEvent changeEvent) {}
+
+    @ApolloConfigChangeListener(value = {ConfigConsts.NAMESPACE_APPLICATION, FX_APOLLO_NAMESPACE},
+        interestedKeys = {"anotherKey"})
+    private void anotherOnChange(ConfigChangeEvent changeEvent) {
+
+    }
+  }
 }
diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializerTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac929482c48ed353bf4940cf651b98f6ad9aa779
--- /dev/null
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializerTest.java
@@ -0,0 +1,95 @@
+package com.ctrip.framework.apollo.spring.boot;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.ctrip.framework.apollo.core.ConfigConsts;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+public class ApolloApplicationContextInitializerTest {
+
+  private ApolloApplicationContextInitializer apolloApplicationContextInitializer;
+
+  @Before
+  public void setUp() throws Exception {
+    apolloApplicationContextInitializer = new ApolloApplicationContextInitializer();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    System.clearProperty("app.id");
+    System.clearProperty(ConfigConsts.APOLLO_CLUSTER_KEY);
+    System.clearProperty("apollo.cacheDir");
+    System.clearProperty(ConfigConsts.APOLLO_META_KEY);
+  }
+
+  @Test
+  public void testFillFromEnvironment() throws Exception {
+    String someAppId = "someAppId";
+    String someCluster = "someCluster";
+    String someCacheDir = "someCacheDir";
+    String someApolloMeta = "someApolloMeta";
+
+    ConfigurableEnvironment environment = mock(ConfigurableEnvironment.class);
+
+    when(environment.getProperty("app.id")).thenReturn(someAppId);
+    when(environment.getProperty(ConfigConsts.APOLLO_CLUSTER_KEY)).thenReturn(someCluster);
+    when(environment.getProperty("apollo.cacheDir")).thenReturn(someCacheDir);
+    when(environment.getProperty(ConfigConsts.APOLLO_META_KEY)).thenReturn(someApolloMeta);
+
+    apolloApplicationContextInitializer.initializeSystemProperty(environment);
+
+    assertEquals(someAppId, System.getProperty("app.id"));
+    assertEquals(someCluster, System.getProperty(ConfigConsts.APOLLO_CLUSTER_KEY));
+    assertEquals(someCacheDir, System.getProperty("apollo.cacheDir"));
+    assertEquals(someApolloMeta, System.getProperty(ConfigConsts.APOLLO_META_KEY));
+  }
+
+  @Test
+  public void testFillFromEnvironmentWithSystemPropertyAlreadyFilled() throws Exception {
+    String someAppId = "someAppId";
+    String someCluster = "someCluster";
+    String someCacheDir = "someCacheDir";
+    String someApolloMeta = "someApolloMeta";
+
+    System.setProperty("app.id", someAppId);
+    System.setProperty(ConfigConsts.APOLLO_CLUSTER_KEY, someCluster);
+    System.setProperty("apollo.cacheDir", someCacheDir);
+    System.setProperty(ConfigConsts.APOLLO_META_KEY, someApolloMeta);
+
+    String anotherAppId = "anotherAppId";
+    String anotherCluster = "anotherCluster";
+    String anotherCacheDir = "anotherCacheDir";
+    String anotherApolloMeta = "anotherApolloMeta";
+
+    ConfigurableEnvironment environment = mock(ConfigurableEnvironment.class);
+
+    when(environment.getProperty("app.id")).thenReturn(anotherAppId);
+    when(environment.getProperty(ConfigConsts.APOLLO_CLUSTER_KEY)).thenReturn(anotherCluster);
+    when(environment.getProperty("apollo.cacheDir")).thenReturn(anotherCacheDir);
+    when(environment.getProperty(ConfigConsts.APOLLO_META_KEY)).thenReturn(anotherApolloMeta);
+
+    apolloApplicationContextInitializer.initializeSystemProperty(environment);
+
+    assertEquals(someAppId, System.getProperty("app.id"));
+    assertEquals(someCluster, System.getProperty(ConfigConsts.APOLLO_CLUSTER_KEY));
+    assertEquals(someCacheDir, System.getProperty("apollo.cacheDir"));
+    assertEquals(someApolloMeta, System.getProperty(ConfigConsts.APOLLO_META_KEY));
+  }
+
+  @Test
+  public void testFillFromEnvironmentWithNoPropertyFromEnvironment() throws Exception {
+    ConfigurableEnvironment environment = mock(ConfigurableEnvironment.class);
+
+    apolloApplicationContextInitializer.initializeSystemProperty(environment);
+
+    assertNull(System.getProperty("app.id"));
+    assertNull(System.getProperty(ConfigConsts.APOLLO_CLUSTER_KEY));
+    assertNull(System.getProperty("apollo.cacheDir"));
+    assertNull(System.getProperty(ConfigConsts.APOLLO_META_KEY));
+  }
+}
diff --git a/apollo-client/src/test/resources/spring/XmlConfigAnnotationTest6.xml b/apollo-client/src/test/resources/spring/XmlConfigAnnotationTest6.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bd7883753efa070894b06a8b3b1e47ea0aa691f1
--- /dev/null
+++ b/apollo-client/src/test/resources/spring/XmlConfigAnnotationTest6.xml
@@ -0,0 +1,10 @@
+
+
+    
+
+    
+
diff --git a/apollo-common/pom.xml b/apollo-common/pom.xml
index da788772ea9311c29df80facff692f8c25737806..62d7bbcc013ff7be4db69518b88f4d69c20cacb5 100644
--- a/apollo-common/pom.xml
+++ b/apollo-common/pom.xml
@@ -4,7 +4,7 @@
     
         com.ctrip.framework.apollo
         apollo
-        0.11.0
+        1.0.0
         ../pom.xml
     
     4.0.0
diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/datasource/TitanSettings.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/datasource/TitanSettings.java
index d0702cbc30f0cc5bad4e3db3b18788d57e695ba5..be429612c73dd87881299d6e16a6ba04d9273e70 100644
--- a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/datasource/TitanSettings.java
+++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/datasource/TitanSettings.java
@@ -30,9 +30,6 @@ public class TitanSettings {
 
   public String getTitanUrl() {
     Env env = EnvUtils.transformEnv(Foundation.server().getEnvType());
-    if (env == null) {
-      return "";
-    }
     switch (env) {
       case FAT:
       case FWS:
@@ -49,9 +46,6 @@ public class TitanSettings {
 
   public String getTitanDbname() {
     Env env = EnvUtils.transformEnv(Foundation.server().getEnvType());
-    if (env == null) {
-      return "";
-    }
     switch (env) {
       case FAT:
       case FWS:
diff --git a/apollo-configservice/pom.xml b/apollo-configservice/pom.xml
index 2e3c7796f45f86b3fc3a3a5f5a36f8219d2c2fb3..59801a9f4159a8fe70a55d25c05d8aef6aacb85f 100644
--- a/apollo-configservice/pom.xml
+++ b/apollo-configservice/pom.xml
@@ -4,7 +4,7 @@
 	
 		com.ctrip.framework.apollo
 		apollo
-		0.11.0
+		1.0.0
 		../pom.xml
 	
 	4.0.0
diff --git a/apollo-configservice/src/main/docker/Dockerfile b/apollo-configservice/src/main/docker/Dockerfile
index 19edc73a60c45b11dbd14f7ae2d928a46dc9fc1b..e25d9e9f4e2a033e2db521868dd4a85ab7738d31 100755
--- a/apollo-configservice/src/main/docker/Dockerfile
+++ b/apollo-configservice/src/main/docker/Dockerfile
@@ -7,7 +7,7 @@
 FROM openjdk:8-jre-alpine
 MAINTAINER ameizi 
 
-ENV VERSION 0.11.0
+ENV VERSION 1.0.0
 
 RUN echo "http://mirrors.aliyun.com/alpine/v3.6/main" > /etc/apk/repositories \
     && echo "http://mirrors.aliyun.com/alpine/v3.6/community" >> /etc/apk/repositories \
diff --git a/apollo-core/pom.xml b/apollo-core/pom.xml
index cb5c0e373572b274018749b8c29e22c7031070de..ee8daea30c1104e7e1095ad8e16147a99e9b4558 100644
--- a/apollo-core/pom.xml
+++ b/apollo-core/pom.xml
@@ -4,7 +4,7 @@
 	
 		com.ctrip.framework.apollo
 		apollo
-		0.11.0
+		1.0.0
 		../pom.xml
 	
 	4.0.0
diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java
index 03b9e9b37e9d649ce1a9d470408f755f77d724d6..4d139fb08de833252065bd70602639a83832d576 100644
--- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java
+++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java
@@ -5,6 +5,7 @@ public interface ConfigConsts {
   String CLUSTER_NAME_DEFAULT = "default";
   String CLUSTER_NAMESPACE_SEPARATOR = "+";
   String APOLLO_CLUSTER_KEY = "apollo.cluster";
+  String APOLLO_META_KEY = "apollo.meta";
   String CONFIG_FILE_CONTENT_KEY = "content";
   String NO_APPID_PLACEHOLDER = "ApolloNoAppIdPlaceHolder";
   long NOTIFICATION_ID_PLACEHOLDER = -1;
diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/MetaDomainConsts.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/MetaDomainConsts.java
index a996b37133bd5cb62d94b0c77e350595df4859b7..f0c0e80a3c857f282cb0794b9960cef3f8355185 100644
--- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/MetaDomainConsts.java
+++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/MetaDomainConsts.java
@@ -1,101 +1,120 @@
 package com.ctrip.framework.apollo.core;
 
 import com.ctrip.framework.apollo.core.enums.Env;
-import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
-import com.ctrip.framework.apollo.core.utils.NetUtil;
-import com.ctrip.framework.apollo.core.utils.ResourceUtils;
-import com.ctrip.framework.apollo.tracer.Tracer;
-import com.ctrip.framework.apollo.tracer.spi.Transaction;
-import com.ctrip.framework.foundation.Foundation;
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import java.util.Collections;
-import java.util.HashMap;
+import java.util.Comparator;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.ctrip.framework.apollo.core.spi.MetaServerProvider;
+import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
+import com.ctrip.framework.apollo.core.utils.NetUtil;
+import com.ctrip.framework.apollo.tracer.Tracer;
+import com.ctrip.framework.apollo.tracer.spi.Transaction;
+import com.ctrip.framework.foundation.internals.ServiceBootstrap;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
 /**
- * The meta domain will load the meta server from System environment first, if not exist, will load from
- * apollo-env.properties. If neither exists, will load the default meta url.
+ * The meta domain will try to load the meta server address from MetaServerProviders, the default ones are:
+ *
+ * 
    + *
  • com.ctrip.framework.apollo.core.internals.LegacyMetaServerProvider
  • + *
* - * Currently, apollo supports local/dev/fat/uat/lpt/pro environments. + * If no provider could provide the meta server url, the default meta url will be used(http://apollo.meta). + *
+ * + * 3rd party MetaServerProvider could be injected by typical Java Service Loader pattern. + * + * @see com.ctrip.framework.apollo.core.internals.LegacyMetaServerProvider */ public class MetaDomainConsts { + public static final String DEFAULT_META_URL = "http://apollo.meta"; - public static final String DEFAULT_META_URL = "http://config.local"; + // env -> meta server address cache + private static final Map metaServerAddressCache = Maps.newConcurrentMap(); + private static volatile List metaServerProviders = null; - private static final long REFRESH_INTERVAL_IN_SECOND = 60;//1 min + private static final long REFRESH_INTERVAL_IN_SECOND = 60;// 1 min private static final Logger logger = LoggerFactory.getLogger(MetaDomainConsts.class); - - private static final Map domains = new HashMap<>(); - private static final Map metaServerAddressCache = Maps.newConcurrentMap(); + // comma separated meta server address -> selected single meta server address cache + private static final Map selectedMetaServerAddressCache = Maps.newConcurrentMap(); private static final AtomicBoolean periodicRefreshStarted = new AtomicBoolean(false); - private static final AtomicBoolean customizedMetaServiceLogged = new AtomicBoolean(false); - static { - initialize(); - } - - static void initialize() { - Properties prop = new Properties(); - prop = ResourceUtils.readConfigFile("apollo-env.properties", prop); - Properties env = System.getProperties(); - domains.put(Env.LOCAL, - env.getProperty("local_meta", prop.getProperty("local.meta", DEFAULT_META_URL))); - domains.put(Env.DEV, - env.getProperty("dev_meta", prop.getProperty("dev.meta", DEFAULT_META_URL))); - domains.put(Env.FAT, - env.getProperty("fat_meta", prop.getProperty("fat.meta", DEFAULT_META_URL))); - domains.put(Env.UAT, - env.getProperty("uat_meta", prop.getProperty("uat.meta", DEFAULT_META_URL))); - domains.put(Env.LPT, - env.getProperty("lpt_meta", prop.getProperty("lpt.meta", DEFAULT_META_URL))); - domains.put(Env.PRO, - env.getProperty("pro_meta", prop.getProperty("pro.meta", DEFAULT_META_URL))); - } + private static final Object LOCK = new Object(); public static String getDomain(Env env) { - // 1. Get meta server address from run time configurations - String metaAddress = getCustomizedMetaServerAddress(); - if (Strings.isNullOrEmpty(metaAddress)) { - // 2. Get meta server address from environment - metaAddress = domains.get(env); - } - // 3. if there is more than one address, need to select one - if (metaAddress != null && metaAddress.contains(",")) { - return selectMetaServerAddress(metaAddress); + String metaServerAddress = getMetaServerAddress(env); + // if there is more than one address, need to select one + if (metaServerAddress.contains(",")) { + return selectMetaServerAddress(metaServerAddress); } - // 4. trim if necessary - if (metaAddress != null) { - metaAddress = metaAddress.trim(); + return metaServerAddress; + } + + private static String getMetaServerAddress(Env env) { + if (!metaServerAddressCache.containsKey(env)) { + initMetaServerAddress(env); } - return metaAddress; + + return metaServerAddressCache.get(env); } - private static String getCustomizedMetaServerAddress() { - // 1. Get from System Property - String metaAddress = System.getProperty("apollo.meta"); - if (Strings.isNullOrEmpty(metaAddress)) { - // 2. Get from OS environment variable - metaAddress = System.getenv("APOLLO_META"); + private static void initMetaServerAddress(Env env) { + if (metaServerProviders == null) { + synchronized (LOCK) { + if (metaServerProviders == null) { + metaServerProviders = initMetaServerProviders(); + } + } } - if (Strings.isNullOrEmpty(metaAddress)) { - metaAddress = Foundation.server().getProperty("apollo.meta", null); + + String metaAddress = null; + + for (MetaServerProvider provider : metaServerProviders) { + metaAddress = provider.getMetaServerAddress(env); + if (!Strings.isNullOrEmpty(metaAddress)) { + logger.info("Located meta server address {} for env {} from {}", metaAddress, env, + provider.getClass().getName()); + break; + } } - if (!Strings.isNullOrEmpty(metaAddress) && customizedMetaServiceLogged.compareAndSet(false, true)) { - logger.warn("Located meta services from apollo.meta configuration: {}, will not use meta services defined in apollo-env.properties!", metaAddress); + if (Strings.isNullOrEmpty(metaAddress)) { + // Fallback to default meta address + metaAddress = DEFAULT_META_URL; + logger.warn( + "Meta server address fallback to {} for env {}, because it is not available in all MetaServerProviders", + metaAddress, env); } - return metaAddress; + metaServerAddressCache.put(env, metaAddress.trim()); + } + + private static List initMetaServerProviders() { + Iterator metaServerProviderIterator = ServiceBootstrap.loadAll(MetaServerProvider.class); + + List metaServerProviders = Lists.newArrayList(metaServerProviderIterator); + + Collections.sort(metaServerProviders, new Comparator() { + @Override + public int compare(MetaServerProvider o1, MetaServerProvider o2) { + // the smaller order has higher priority + return Integer.compare(o1.getOrder(), o2.getOrder()); + } + }); + + return metaServerProviders; } /** @@ -104,18 +123,18 @@ public class MetaDomainConsts { * *
* - * In production environment, we still suggest using one single domain - * like http://config.xxx.com(backed by software load balancers like nginx) instead of multiple ip addresses + * In production environment, we still suggest using one single domain like http://config.xxx.com(backed by software + * load balancers like nginx) instead of multiple ip addresses */ private static String selectMetaServerAddress(String metaServerAddresses) { - String metaAddressSelected = metaServerAddressCache.get(metaServerAddresses); + String metaAddressSelected = selectedMetaServerAddressCache.get(metaServerAddresses); if (metaAddressSelected == null) { - //initialize + // initialize if (periodicRefreshStarted.compareAndSet(false, true)) { schedulePeriodicRefresh(); } updateMetaServerAddresses(metaServerAddresses); - metaAddressSelected = metaServerAddressCache.get(metaServerAddresses); + metaAddressSelected = selectedMetaServerAddressCache.get(metaServerAddresses); } return metaAddressSelected; @@ -129,7 +148,7 @@ public class MetaDomainConsts { try { List metaServers = Lists.newArrayList(metaServerAddresses.split(",")); - //random load balancing + // random load balancing Collections.shuffle(metaServers); boolean serverAvailable = false; @@ -137,22 +156,22 @@ public class MetaDomainConsts { for (String address : metaServers) { address = address.trim(); if (NetUtil.pingUrl(address)) { - //select the first available meta server - metaServerAddressCache.put(metaServerAddresses, address); + // select the first available meta server + selectedMetaServerAddressCache.put(metaServerAddresses, address); serverAvailable = true; logger.debug("Selected meta server address {} for {}", address, metaServerAddresses); break; } } - //we need to make sure the map is not empty, e.g. the first update might be failed - if (!metaServerAddressCache.containsKey(metaServerAddresses)) { - metaServerAddressCache.put(metaServerAddresses, metaServers.get(0).trim()); + // we need to make sure the map is not empty, e.g. the first update might be failed + if (!selectedMetaServerAddressCache.containsKey(metaServerAddresses)) { + selectedMetaServerAddressCache.put(metaServerAddresses, metaServers.get(0).trim()); } if (!serverAvailable) { - logger.warn("Could not find available meta server for configured meta server addresses: {}, fallback to: {}", metaServerAddresses, - metaServerAddressCache.get(metaServerAddresses)); + logger.warn("Could not find available meta server for configured meta server addresses: {}, fallback to: {}", + metaServerAddresses, selectedMetaServerAddressCache.get(metaServerAddresses)); } transaction.setStatus(Transaction.SUCCESS); @@ -165,21 +184,19 @@ public class MetaDomainConsts { } private static void schedulePeriodicRefresh() { - ScheduledExecutorService scheduledExecutorService = Executors - .newScheduledThreadPool(1, ApolloThreadFactory.create("MetaServiceLocator", true)); + ScheduledExecutorService scheduledExecutorService = + Executors.newScheduledThreadPool(1, ApolloThreadFactory.create("MetaServiceLocator", true)); scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { - for (String metaServerAddresses : metaServerAddressCache.keySet()) { + for (String metaServerAddresses : selectedMetaServerAddressCache.keySet()) { updateMetaServerAddresses(metaServerAddresses); } } catch (Throwable ex) { - logger.warn( - String.format("Refreshing meta server address failed, will retry in %d seconds", - REFRESH_INTERVAL_IN_SECOND), ex - ); + logger.warn(String.format("Refreshing meta server address failed, will retry in %d seconds", + REFRESH_INTERVAL_IN_SECOND), ex); } } }, REFRESH_INTERVAL_IN_SECOND, REFRESH_INTERVAL_IN_SECOND, TimeUnit.SECONDS); diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/Env.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/Env.java index dfa30b8e835cfd681d48f1b558d2458d02c0c6fd..9fcc0b8812ce414d4f54f198b583a391ec9b7f33 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/Env.java +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/Env.java @@ -19,11 +19,11 @@ import com.google.common.base.Preconditions; * @author Jason Song(song_s@ctrip.com) */ public enum Env{ - LOCAL, DEV, FWS, FAT, UAT, LPT, PRO, TOOLS; + LOCAL, DEV, FWS, FAT, UAT, LPT, PRO, TOOLS, UNKNOWN; public static Env fromString(String env) { Env environment = EnvUtils.transformEnv(env); - Preconditions.checkArgument(environment != null, String.format("Env %s is invalid", env)); + Preconditions.checkArgument(environment != UNKNOWN, String.format("Env %s is invalid", env)); return environment; } } diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/EnvUtils.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/EnvUtils.java index 3795f4396b685db6f1746ed3507ae5e3b2ca053a..de760a289accc49526ef59ac12c53c690ec3d237 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/EnvUtils.java +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/EnvUtils.java @@ -6,7 +6,7 @@ public final class EnvUtils { public static Env transformEnv(String envName) { if (StringUtils.isBlank(envName)) { - return null; + return Env.UNKNOWN; } switch (envName.trim().toUpperCase()) { case "LPT": @@ -26,7 +26,7 @@ public final class EnvUtils { case "TOOLS": return Env.TOOLS; default: - return null; + return Env.UNKNOWN; } } } diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/internals/LegacyMetaServerProvider.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/internals/LegacyMetaServerProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..4d01af3dcfc484b6432dd39f37458e6e24ee4d0e --- /dev/null +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/internals/LegacyMetaServerProvider.java @@ -0,0 +1,51 @@ +package com.ctrip.framework.apollo.core.internals; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import com.ctrip.framework.apollo.core.enums.Env; +import com.ctrip.framework.apollo.core.spi.MetaServerProvider; +import com.ctrip.framework.apollo.core.utils.ResourceUtils; + +/** + * For legacy meta server configuration use, i.e. apollo-env.properties + */ +public class LegacyMetaServerProvider implements MetaServerProvider { + // make it as lowest as possible, yet not the lowest + public static final int ORDER = MetaServerProvider.LOWEST_PRECEDENCE - 1; + private static final Map domains = new HashMap<>(); + + public LegacyMetaServerProvider() { + initialize(); + } + + private void initialize() { + Properties prop = new Properties(); + prop = ResourceUtils.readConfigFile("apollo-env.properties", prop); + Properties env = System.getProperties(); + domains.put(Env.LOCAL, + env.getProperty("local_meta", prop.getProperty("local.meta"))); + domains.put(Env.DEV, + env.getProperty("dev_meta", prop.getProperty("dev.meta"))); + domains.put(Env.FAT, + env.getProperty("fat_meta", prop.getProperty("fat.meta"))); + domains.put(Env.UAT, + env.getProperty("uat_meta", prop.getProperty("uat.meta"))); + domains.put(Env.LPT, + env.getProperty("lpt_meta", prop.getProperty("lpt.meta"))); + domains.put(Env.PRO, + env.getProperty("pro_meta", prop.getProperty("pro.meta"))); + } + + @Override + public String getMetaServerAddress(Env targetEnv) { + String metaServerAddress = domains.get(targetEnv); + return metaServerAddress == null ? null : metaServerAddress.trim(); + } + + @Override + public int getOrder() { + return ORDER; + } +} diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/spi/MetaServerProvider.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/spi/MetaServerProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..31c0521a5b1f6c680dc592e9ede3a997d427d822 --- /dev/null +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/spi/MetaServerProvider.java @@ -0,0 +1,13 @@ +package com.ctrip.framework.apollo.core.spi; + +import com.ctrip.framework.apollo.core.enums.Env; + +public interface MetaServerProvider extends Ordered { + + /** + * Provide the Apollo meta server address, could be a domain url or comma separated ip addresses, like http://1.2.3.4:8080,http://2.3.4.5:8080. + *
+ * In production environment, we suggest using one single domain like http://config.xxx.com(backed by software load balancers like nginx) instead of multiple ip addresses + */ + String getMetaServerAddress(Env targetEnv); +} diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/spi/Ordered.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/spi/Ordered.java new file mode 100644 index 0000000000000000000000000000000000000000..248e117069d34f8144c64b1ba424b489b721cb18 --- /dev/null +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/spi/Ordered.java @@ -0,0 +1,37 @@ +package com.ctrip.framework.apollo.core.spi; + +/** + * {@code Ordered} is an interface that can be implemented by objects that + * should be orderable, for example in a {@code Collection}. + * + *

The actual {@link #getOrder() order} can be interpreted as prioritization, + * with the first object (with the lowest order value) having the highest + * priority. + */ +public interface Ordered { + /** + * Useful constant for the highest precedence value. + * @see java.lang.Integer#MIN_VALUE + */ + int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; + + /** + * Useful constant for the lowest precedence value. + * @see java.lang.Integer#MAX_VALUE + */ + int LOWEST_PRECEDENCE = Integer.MAX_VALUE; + + + /** + * Get the order value of this object. + *

Higher values are interpreted as lower priority. As a consequence, + * the object with the lowest value has the highest priority (somewhat + * analogous to Servlet {@code load-on-startup} values). + *

Same order values will result in arbitrary sort positions for the + * affected objects. + * @return the order value + * @see #HIGHEST_PRECEDENCE + * @see #LOWEST_PRECEDENCE + */ + int getOrder(); +} diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/utils/ResourceUtils.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/utils/ResourceUtils.java index 45fde2e3747b17e6555aba7d489b4939abfd9615..718e0f25e4c0e730e49ea8c610b62e6b0610706a 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/utils/ResourceUtils.java +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/utils/ResourceUtils.java @@ -51,7 +51,7 @@ public class ResourceUtils { if (sb.length() > 0) { logger.debug("Reading properties: \n" + sb.toString()); } else { - logger.warn("No available properties"); + logger.warn("No available properties: {}", configPath); } } return props; diff --git a/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/ServiceBootstrap.java b/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/ServiceBootstrap.java index 523956be54877ab9d73c3702682f14fea5850379..a34a518cfab6cdd1705831f537878b92863c347d 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/ServiceBootstrap.java +++ b/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/ServiceBootstrap.java @@ -14,7 +14,7 @@ public class ServiceBootstrap { return iterator.next(); } - private static Iterator loadAll(Class clazz) { + public static Iterator loadAll(Class clazz) { ServiceLoader loader = ServiceLoader.load(clazz); return loader.iterator(); diff --git a/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultApplicationProvider.java b/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultApplicationProvider.java index e4ffb820eb33f9b5eb5676e47d901bb9fbbc2489..cbe3cf0408790e94193d8c350b9d8e7e32ae9777 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultApplicationProvider.java +++ b/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultApplicationProvider.java @@ -28,9 +28,6 @@ public class DefaultApplicationProvider implements ApplicationProvider { in = DefaultApplicationProvider.class.getResourceAsStream(APP_PROPERTIES_CLASSPATH); } - if (in == null) { - logger.warn("{} not found from classpath!", APP_PROPERTIES_CLASSPATH); - } initialize(in); } catch (Throwable ex) { logger.error("Initialize DefaultApplicationProvider failed.", ex); diff --git a/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultServerProvider.java b/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultServerProvider.java index d8263998871126c2ed7129974721e6e0b2ad7797..8b939621f1060a1a56e5f4a8cd68db57e6bd9dce 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultServerProvider.java +++ b/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultServerProvider.java @@ -37,7 +37,6 @@ public class DefaultServerProvider implements ServerProvider { return; } - logger.warn("{} does not exist or is not readable.", path); initialize(null); } catch (Throwable ex) { logger.error("Initialize DefaultServerProvider failed.", ex); @@ -128,7 +127,7 @@ public class DefaultServerProvider implements ServerProvider { // 4. Set environment to null. m_env = null; - logger.warn("Environment is set to null. Because it is not available in either (1) JVM system property 'env', (2) OS env variable 'ENV' nor (3) property 'env' from the properties InputStream."); + logger.info("Environment is set to null. Because it is not available in either (1) JVM system property 'env', (2) OS env variable 'ENV' nor (3) property 'env' from the properties InputStream."); } private void initDataCenter() { diff --git a/apollo-core/src/main/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider b/apollo-core/src/main/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider new file mode 100644 index 0000000000000000000000000000000000000000..43484be958502f6b642c69bf62364e8180ec2442 --- /dev/null +++ b/apollo-core/src/main/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider @@ -0,0 +1 @@ +com.ctrip.framework.apollo.core.internals.LegacyMetaServerProvider diff --git a/apollo-core/src/test/java/com/ctrip/framework/apollo/core/MetaDomainTest.java b/apollo-core/src/test/java/com/ctrip/framework/apollo/core/MetaDomainTest.java index bc94713857524c700a9582e3955bf77633868949..67080420c673157cacfe5b169c2a190db82b3630 100644 --- a/apollo-core/src/test/java/com/ctrip/framework/apollo/core/MetaDomainTest.java +++ b/apollo-core/src/test/java/com/ctrip/framework/apollo/core/MetaDomainTest.java @@ -5,6 +5,10 @@ import static org.junit.Assert.assertTrue; import com.ctrip.framework.apollo.BaseIntegrationTest; import com.ctrip.framework.apollo.core.enums.Env; +import com.ctrip.framework.apollo.core.internals.LegacyMetaServerProvider; +import com.ctrip.framework.apollo.core.spi.MetaServerProvider; +import com.google.common.collect.Maps; +import java.util.Map; import javax.servlet.http.HttpServletResponse; import org.junit.After; import org.junit.Test; @@ -15,10 +19,7 @@ public class MetaDomainTest extends BaseIntegrationTest { @After public void tearDown() throws Exception { super.tearDown(); - System.clearProperty("fat_meta"); - System.clearProperty("uat_meta"); - System.clearProperty("lpt_meta"); - System.clearProperty("apollo.meta"); + MockMetaServerProvider.clear(); } @Test @@ -28,16 +29,6 @@ public class MetaDomainTest extends BaseIntegrationTest { assertEquals(MetaDomainConsts.DEFAULT_META_URL, MetaDomainConsts.getDomain(Env.PRO)); } - @Test - public void testGetMetaDomainWithSystemProperty() throws Exception { - String someMeta = "some-meta"; - Env someEnv = Env.DEV; - - System.setProperty("apollo.meta", someMeta); - - assertEquals(someMeta, MetaDomainConsts.getDomain(someEnv)); - } - @Test public void testGetValidAddress() throws Exception { String someResponse = "some response"; @@ -46,10 +37,8 @@ public class MetaDomainTest extends BaseIntegrationTest { String validServer = " http://localhost:" + PORT + " "; String invalidServer = "http://localhost:" + findFreePort(); - System.setProperty("fat_meta", validServer + "," + invalidServer); - System.setProperty("uat_meta", invalidServer + "," + validServer); - - MetaDomainConsts.initialize(); + MockMetaServerProvider.mock(Env.FAT, validServer + "," + invalidServer); + MockMetaServerProvider.mock(Env.UAT, invalidServer + "," + validServer); assertEquals(validServer.trim(), MetaDomainConsts.getDomain(Env.FAT)); assertEquals(validServer.trim(), MetaDomainConsts.getDomain(Env.UAT)); @@ -60,12 +49,33 @@ public class MetaDomainTest extends BaseIntegrationTest { String invalidServer = "http://localhost:" + findFreePort() + " "; String anotherInvalidServer = "http://localhost:" + findFreePort() + " "; - System.setProperty("lpt_meta", invalidServer + "," + anotherInvalidServer); - - MetaDomainConsts.initialize(); + MockMetaServerProvider.mock(Env.LPT, invalidServer + "," + anotherInvalidServer); String metaServer = MetaDomainConsts.getDomain(Env.LPT); assertTrue(metaServer.equals(invalidServer.trim()) || metaServer.equals(anotherInvalidServer.trim())); } + + public static class MockMetaServerProvider implements MetaServerProvider { + + private static Map mockMetaServerAddress = Maps.newHashMap(); + + private static void mock(Env env, String metaServerAddress) { + mockMetaServerAddress.put(env, metaServerAddress); + } + + private static void clear() { + mockMetaServerAddress.clear(); + } + + @Override + public String getMetaServerAddress(Env targetEnv) { + return mockMetaServerAddress.get(targetEnv); + } + + @Override + public int getOrder() { + return LegacyMetaServerProvider.ORDER - 1;// just in front of LegacyMetaServerProvider + } + } } diff --git a/apollo-core/src/test/java/com/ctrip/framework/apollo/core/enums/EnvUtilsTest.java b/apollo-core/src/test/java/com/ctrip/framework/apollo/core/enums/EnvUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..aeac65753b5c5857ee98adfa07b4c2a14e342b5f --- /dev/null +++ b/apollo-core/src/test/java/com/ctrip/framework/apollo/core/enums/EnvUtilsTest.java @@ -0,0 +1,28 @@ +package com.ctrip.framework.apollo.core.enums; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class EnvUtilsTest { + + @Test + public void testTransformEnv() throws Exception { + assertEquals(Env.DEV, EnvUtils.transformEnv(Env.DEV.name())); + assertEquals(Env.FAT, EnvUtils.transformEnv(Env.FAT.name().toLowerCase())); + assertEquals(Env.UAT, EnvUtils.transformEnv(" " + Env.UAT.name().toUpperCase() + "")); + assertEquals(Env.UNKNOWN, EnvUtils.transformEnv("someInvalidEnv")); + } + + @Test + public void testFromString() throws Exception { + assertEquals(Env.DEV, Env.fromString(Env.DEV.name())); + assertEquals(Env.FAT, Env.fromString(Env.FAT.name().toLowerCase())); + assertEquals(Env.UAT, Env.fromString(" " + Env.UAT.name().toUpperCase() + "")); + } + + @Test(expected = IllegalArgumentException.class) + public void testFromInvalidString() throws Exception { + Env.fromString("someInvalidEnv"); + } +} diff --git a/apollo-core/src/test/java/com/ctrip/framework/apollo/core/internals/LegacyMetaServerProviderTest.java b/apollo-core/src/test/java/com/ctrip/framework/apollo/core/internals/LegacyMetaServerProviderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4445e6a3d5be50c60423a9b7ffe38947f7900b80 --- /dev/null +++ b/apollo-core/src/test/java/com/ctrip/framework/apollo/core/internals/LegacyMetaServerProviderTest.java @@ -0,0 +1,36 @@ +package com.ctrip.framework.apollo.core.internals; + +import static org.junit.Assert.assertEquals; + +import com.ctrip.framework.apollo.core.enums.Env; +import org.junit.After; +import org.junit.Test; + +public class LegacyMetaServerProviderTest { + + @After + public void tearDown() throws Exception { + System.clearProperty("dev_meta"); + } + + @Test + public void testFromPropertyFile() { + LegacyMetaServerProvider legacyMetaServerProvider = new LegacyMetaServerProvider(); + assertEquals("http://localhost:8080", legacyMetaServerProvider.getMetaServerAddress(Env.LOCAL)); + assertEquals("http://dev:8080", legacyMetaServerProvider.getMetaServerAddress(Env.DEV)); + assertEquals(null, legacyMetaServerProvider.getMetaServerAddress(Env.PRO)); + } + + @Test + public void testWithSystemProperty() throws Exception { + String someDevMetaAddress = "someMetaAddress"; + String someFatMetaAddress = "someFatMetaAddress"; + System.setProperty("dev_meta", someDevMetaAddress); + System.setProperty("fat_meta", someFatMetaAddress); + + LegacyMetaServerProvider legacyMetaServerProvider = new LegacyMetaServerProvider(); + + assertEquals(someDevMetaAddress, legacyMetaServerProvider.getMetaServerAddress(Env.DEV)); + assertEquals(someFatMetaAddress, legacyMetaServerProvider.getMetaServerAddress(Env.FAT)); + } +} diff --git a/apollo-core/src/test/java/com/ctrip/framework/foundation/internals/ServiceBootstrapTest.java b/apollo-core/src/test/java/com/ctrip/framework/foundation/internals/ServiceBootstrapTest.java index 81313ef25ccd3702d52d27cf392da6702da7e79c..b3b8ed617c354ea8e6790a04e61796f53e960fd3 100644 --- a/apollo-core/src/test/java/com/ctrip/framework/foundation/internals/ServiceBootstrapTest.java +++ b/apollo-core/src/test/java/com/ctrip/framework/foundation/internals/ServiceBootstrapTest.java @@ -1,6 +1,5 @@ package com.ctrip.framework.foundation.internals; -import com.ctrip.framework.foundation.internals.ServiceBootstrap; import org.junit.Test; import java.util.ServiceConfigurationError; diff --git a/apollo-core/src/test/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider b/apollo-core/src/test/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider new file mode 100644 index 0000000000000000000000000000000000000000..bd6d23f18db4e1785016935594ff6bebf2d736d8 --- /dev/null +++ b/apollo-core/src/test/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider @@ -0,0 +1 @@ +com.ctrip.framework.apollo.core.MetaDomainTest$MockMetaServerProvider diff --git a/apollo-demo/pom.xml b/apollo-demo/pom.xml index 30fb4dee3dd858a34164ae61a86ccbb24e894eb1..16ae68fc60fede8e058d415a0c49effb73c0c290 100644 --- a/apollo-demo/pom.xml +++ b/apollo-demo/pom.xml @@ -4,7 +4,7 @@ apollo com.ctrip.framework.apollo - 0.11.0 + 1.0.0 4.0.0 apollo-demo diff --git a/apollo-portal/pom.xml b/apollo-portal/pom.xml index 68b59fe7888fa188ab020d1e6779b50a3f0283ac..644c0ae7ca25fdfe375ecf36908c15f9d5a5cb43 100644 --- a/apollo-portal/pom.xml +++ b/apollo-portal/pom.xml @@ -4,7 +4,7 @@ com.ctrip.framework.apollo apollo - 0.11.0 + 1.0.0 ../pom.xml 4.0.0 diff --git a/apollo-portal/src/main/config/apollo-env.properties b/apollo-portal/src/main/config/apollo-env.properties deleted file mode 100644 index 0c9448882ba51aceadaa73cfe7a585a8ba568562..0000000000000000000000000000000000000000 --- a/apollo-portal/src/main/config/apollo-env.properties +++ /dev/null @@ -1,6 +0,0 @@ -local.meta=http://localhost:8080 -dev.meta=${dev_meta} -fat.meta=${fat_meta} -uat.meta=${uat_meta} -lpt.meta=${lpt_meta} -pro.meta=${pro_meta} diff --git a/apollo-portal/src/main/docker/Dockerfile b/apollo-portal/src/main/docker/Dockerfile index d9bc956ee85947ba377615dcb9f7826c3dca2d08..d9831731b00450e6d22324989f95ab6fd12f70ad 100755 --- a/apollo-portal/src/main/docker/Dockerfile +++ b/apollo-portal/src/main/docker/Dockerfile @@ -9,7 +9,7 @@ FROM openjdk:8-jre-alpine MAINTAINER ameizi -ENV VERSION 0.11.0 +ENV VERSION 1.0.0 RUN echo "http://mirrors.aliyun.com/alpine/v3.6/main" > /etc/apk/repositories \ && echo "http://mirrors.aliyun.com/alpine/v3.6/community" >> /etc/apk/repositories \ diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java index 84ba65d5dd8bbcd8aa283dc8a597c2e62586d1e8..878ba58a22816bb579a8ced3427098d4878ad1ff 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java @@ -2,6 +2,7 @@ package com.ctrip.framework.apollo.portal.controller; import com.ctrip.framework.apollo.common.dto.NamespaceDTO; import com.ctrip.framework.apollo.common.exception.BadRequestException; +import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.EnvUtils; import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.openapi.entity.Consumer; @@ -90,7 +91,7 @@ public class ConsumerController { if (Strings.isNullOrEmpty(env)) { continue; } - if (null == EnvUtils.transformEnv(env)) { + if (Env.UNKNOWN == EnvUtils.transformEnv(env)) { throw new BadRequestException(String.format("env: %s is illegal", env)); } envList.add(env); diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/PermissionController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/PermissionController.java index 73ddce86285ab191b215ab939f98b6cf2019d1c8..6c29b417a8c27da2a89dc3166e164e0437da61d8 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/PermissionController.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/PermissionController.java @@ -98,7 +98,7 @@ public class PermissionController { public NamespaceEnvRolesAssignedUsers getNamespaceEnvRoles(@PathVariable String appId, @PathVariable String env, @PathVariable String namespaceName) { // validate env parameter - if (null == EnvUtils.transformEnv(env)) { + if (Env.UNKNOWN == EnvUtils.transformEnv(env)) { throw new BadRequestException("env is illegal"); } @@ -130,7 +130,7 @@ public class PermissionController { } // validate env parameter - if (null == EnvUtils.transformEnv(env)) { + if (Env.UNKNOWN == EnvUtils.transformEnv(env)) { throw new BadRequestException("env is illegal"); } Set assignedUser = rolePermissionService.assignRoleToUsers(RoleUtils.buildNamespaceRoleName(appId, namespaceName, roleType, env), @@ -152,7 +152,7 @@ public class PermissionController { throw new BadRequestException("role type is illegal"); } // validate env parameter - if (null == EnvUtils.transformEnv(env)) { + if (Env.UNKNOWN == EnvUtils.transformEnv(env)) { throw new BadRequestException("env is illegal"); } rolePermissionService.removeRoleFromUsers(RoleUtils.buildNamespaceRoleName(appId, namespaceName, roleType, env), diff --git a/apollo-core/src/main/resources/apollo-env.properties b/apollo-portal/src/main/resources/apollo-env.properties similarity index 100% rename from apollo-core/src/main/resources/apollo-env.properties rename to apollo-portal/src/main/resources/apollo-env.properties diff --git a/doc/images/known-users/meiyou.png b/doc/images/known-users/meiyou.png new file mode 100644 index 0000000000000000000000000000000000000000..dae9a43c585d03b71beed695205a98bc9fcffb8e Binary files /dev/null and b/doc/images/known-users/meiyou.png differ diff --git a/pom.xml b/pom.xml index a104e478103fd8ac7f90f1c03d78cd4c67586f8c..39ef45e0b8a75308e9d345d88350acd365c78e90 100644 --- a/pom.xml +++ b/pom.xml @@ -5,11 +5,11 @@ com.ctrip.framework.apollo apollo - 0.11.0 + 1.0.0 Apollo pom Ctrip Configuration Center - https://ctripcorp.github.io/apollo + https://github.com/ctripcorp/apollo Ctrip, Inc. @@ -85,6 +85,8 @@ 3.0.0 2.5.2 2.8.2 + 3.0.1 + 1.6 github ${env.GITHUB_OAUTH_TOKEN} @@ -348,6 +350,30 @@ maven-jar-plugin ${maven-jar-plugin.version} + + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadoc + + jar + + + none + + + + + public + UTF-8 + UTF-8 + UTF-8 + + http://docs.oracle.com/javase/7/docs/api + + + org.apache.maven.plugins @@ -366,6 +392,20 @@ maven-deploy-plugin ${maven-deploy-plugin.version} + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + verify + + sign + + + + org.springframework.boot spring-boot-maven-plugin @@ -378,29 +418,6 @@ - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.17 - - - com.puppycrawl.tools - checkstyle - 6.18 - - - com.ctrip.framework.apollo - apollo-buildtools - ${project.version} - - - - google_checks.xml - LICENSE-2.0.txt - false - true - - org.codehaus.mojo findbugs-maven-plugin @@ -462,10 +479,6 @@ org.apache.maven.plugins maven-deploy-plugin - - org.apache.maven.plugins - maven-checkstyle-plugin - org.codehaus.mojo findbugs-maven-plugin @@ -531,7 +544,6 @@ true application-github.properties - apollo-env.properties @@ -539,7 +551,6 @@ false application-github.properties - apollo-env.properties @@ -815,6 +826,21 @@ + + release + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + org.apache.maven.plugins + maven-gpg-plugin + + + + diff --git a/scripts/build.bat b/scripts/build.bat index 290eced0da39edbe9dc0b5db9e34dbbe9c23a036..b28dfcfe76013ca867668f50cd819dbc8f7c6587 100644 --- a/scripts/build.bat +++ b/scripts/build.bat @@ -37,10 +37,4 @@ call mvn clean package -DskipTests -pl apollo-portal -am -Dapollo_profile=github echo "==== building portal finished ====" -echo "==== starting to build client ====" - -call mvn clean install -DskipTests -pl apollo-client -am %META_SERVERS_OPTS% - -echo "==== building client finished ====" - pause diff --git a/scripts/build.sh b/scripts/build.sh index 9f3e2a99fc6c7e3824fce3d32752c3bb55dee433..cb6a539ba1b198c4a1bc69e7b5bb20c9f72ef40d 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -36,10 +36,3 @@ echo "==== starting to build portal ====" mvn clean package -DskipTests -pl apollo-portal -am -Dapollo_profile=github,auth -Dspring_datasource_url=$apollo_portal_db_url -Dspring_datasource_username=$apollo_portal_db_username -Dspring_datasource_password=$apollo_portal_db_password $META_SERVERS_OPTS echo "==== building portal finished ====" - -echo "==== starting to build client ====" - -mvn clean install -DskipTests -pl apollo-client -am $META_SERVERS_OPTS - -echo "==== building client finished ====" -