Repository: ctripcorp/apollo
Pull request #1340 (2018-08-07 13:09:39)

merge 1.x to master


Patch:
diff --git a/apollo-adminservice/pom.xml b/apollo-adminservice/pom.xml
index ac2ddfd93..5ff36d455 100644
--- a/apollo-adminservice/pom.xml
+++ b/apollo-adminservice/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.11.0</version>
+		<version>1.0.0</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/apollo-adminservice/src/main/docker/Dockerfile b/apollo-adminservice/src/main/docker/Dockerfile
index 0706bed3c..4c27c7bfb 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 <sxyx2008@163.com>
 
-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 733dccaf7..cf102ed5b 100644
--- a/apollo-assembly/pom.xml
+++ b/apollo-assembly/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.11.0</version>
+		<version>1.0.0</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/apollo-biz/pom.xml b/apollo-biz/pom.xml
index c6cfce695..25c83fe3b 100644
--- a/apollo-biz/pom.xml
+++ b/apollo-biz/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.11.0</version>
+		<version>1.0.0</version>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
 	<artifactId>apollo-biz</artifactId>
diff --git a/apollo-buildtools/pom.xml b/apollo-buildtools/pom.xml
index b888ec699..984233e8f 100644
--- a/apollo-buildtools/pom.xml
+++ b/apollo-buildtools/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.11.0</version>
+		<version>1.0.0</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/apollo-client/README.md b/apollo-client/README.md
index fd4686d85..afcf7eea1 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:
 		<dependency>
 			<groupId>com.ctrip.framework.apollo</groupId>
 			<artifactId>apollo-client</artifactId>
-			<version>0.11.0</version>
+			<version>1.0.0</version>
 		</dependency>
 
 ## III. Client Usage
diff --git a/apollo-client/pom.xml b/apollo-client/pom.xml
index fe35958db..87df278a2 100644
--- a/apollo-client/pom.xml
+++ b/apollo-client/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.11.0</version>
+		<version>1.0.0</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
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 7eaff8eac..ec779d430 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<String> 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 965301c94..a1a333288 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<ConfigChangeListener> m_listeners = Lists.newCopyOnWriteArrayList();
-  private ConfigUtil m_configUtil;
+  private final List<ConfigChangeListener> m_listeners = Lists.newCopyOnWriteArrayList();
+  private final Map<ConfigChangeListener, Set<String>> m_interestedKeys = Maps.newConcurrentMap();
+  private final ConfigUtil m_configUtil;
   private volatile Cache<String, Integer> m_integerCache;
   private volatile Cache<String, Long> m_longCache;
   private volatile Cache<String, Short> m_shortCache;
@@ -53,9 +54,9 @@ public abstract class AbstractConfig implements Config {
   private volatile Cache<String, Boolean> m_booleanCache;
   private volatile Cache<String, Date> m_dateCache;
   private volatile Cache<String, Long> m_durationCache;
-  private Map<String, Cache<String, String[]>> m_arrayCache;
-  private List<Cache> allCaches;
-  private AtomicLong m_configVersion; //indicate config version
+  private final Map<String, Cache<String, String[]>> m_arrayCache;
+  private final List<Cache> 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<String> 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<String> 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<ConfigChange> 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 000000000..3c2bd8daf
--- /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 9ceddc082..d8a8cd332 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<String> 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 02e2a9478..972672f85 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;
  *
  * <p>Usage example:</p>
  * <pre class="code">
- * //Listener on namespaces of "someNamespace" and "anotherNamespace"
+ * //Listener on namespaces of "someNamespace" and "anotherNamespace", will be notified when any key is changed
  * &#064;ApolloConfigChangeListener({"someNamespace","anotherNamespace"})
  * private void onChange(ConfigChangeEvent changeEvent) {
  *     //handle change event
  * }
+ * <br />
+ * //Listener on namespaces of "someNamespace" and "anotherNamespace", will only be notified when "someKey" or "anotherKey" is changed
+ * &#064;ApolloConfigChangeListener(value = {"someNamespace","anotherNamespace"}, interestedKeys = {"someKey", "anotherKey"})
+ * private void onChange(ConfigChangeEvent changeEvent) {
+ *     //handle change event
+ * }
  * </pre>
  *
  * @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.
+   * <br />
+   * 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 3efb59772..908792b30 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
  *
  * <p>Configuration example:</p>
  * <pre class="code">
- *   # 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
  * </pre>
  *
  * or
  *
  * <pre class="code">
+ *   # 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<ConfigurableApplicationContext> {
   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 96663f8e2..174c354e2 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 000000000..35db9bfc4
--- /dev/null
+++ b/apollo-client/src/main/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider
@@ -0,0 +1,1 @@
+com.ctrip.framework.apollo.internals.DefaultMetaServerProvider
diff --git a/apollo-common/pom.xml b/apollo-common/pom.xml
index da788772e..62d7bbcc0 100644
--- a/apollo-common/pom.xml
+++ b/apollo-common/pom.xml
@@ -4,7 +4,7 @@
     <parent>
         <groupId>com.ctrip.framework.apollo</groupId>
         <artifactId>apollo</artifactId>
-        <version>0.11.0</version>
+        <version>1.0.0</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
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 d0702cbc3..be429612c 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 2e3c7796f..59801a9f4 100644
--- a/apollo-configservice/pom.xml
+++ b/apollo-configservice/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.11.0</version>
+		<version>1.0.0</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
diff --git a/apollo-configservice/src/main/docker/Dockerfile b/apollo-configservice/src/main/docker/Dockerfile
index 19edc73a6..e25d9e9f4 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 <sxyx2008@163.com>
 
-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 cb5c0e373..ee8daea30 100644
--- a/apollo-core/pom.xml
+++ b/apollo-core/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.11.0</version>
+		<version>1.0.0</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
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 03b9e9b37..4d139fb08 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 a996b3713..f0c0e80a3 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:
+ *
+ * <ul>
+ * <li>com.ctrip.framework.apollo.core.internals.LegacyMetaServerProvider</li>
+ * </ul>
  *
- * 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).
+ * <br />
+ *
+ * 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<Env, String> metaServerAddressCache = Maps.newConcurrentMap();
+  private static volatile List<MetaServerProvider> 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<Env, String> domains = new HashMap<>();
-  private static final Map<String, String> metaServerAddressCache = Maps.newConcurrentMap();
+  // comma separated meta server address -> selected single meta server address cache
+  private static final Map<String, String> 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<MetaServerProvider> initMetaServerProviders() {
+    Iterator<MetaServerProvider> metaServerProviderIterator = ServiceBootstrap.loadAll(MetaServerProvider.class);
+
+    List<MetaServerProvider> metaServerProviders = Lists.newArrayList(metaServerProviderIterator);
+
+    Collections.sort(metaServerProviders, new Comparator<MetaServerProvider>() {
+      @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 {
    *
    * <br />
    *
-   * 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<String> 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 dfa30b8e8..9fcc0b881 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 3795f4396..de760a289 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 000000000..4d01af3dc
--- /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<Env, String> 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 000000000..31c0521a5
--- /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.
+   * <br/>
+   * 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 000000000..248e11706
--- /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 <em>orderable</em>, for example in a {@code Collection}.
+ *
+ * <p>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.
+   * <p>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).
+   * <p>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 45fde2e37..718e0f25e 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 523956be5..a34a518cf 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 <S> Iterator<S> loadAll(Class<S> clazz) {
+  public static <S> Iterator<S> loadAll(Class<S> clazz) {
     ServiceLoader<S> 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 e4ffb820e..cbe3cf040 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 d82639988..8b939621f 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 000000000..43484be95
--- /dev/null
+++ b/apollo-core/src/main/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider
@@ -0,0 +1,1 @@
+com.ctrip.framework.apollo.core.internals.LegacyMetaServerProvider
diff --git a/apollo-demo/pom.xml b/apollo-demo/pom.xml
index 30fb4dee3..16ae68fc6 100644
--- a/apollo-demo/pom.xml
+++ b/apollo-demo/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>apollo</artifactId>
 		<groupId>com.ctrip.framework.apollo</groupId>
-		<version>0.11.0</version>
+		<version>1.0.0</version>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
 	<artifactId>apollo-demo</artifactId>
diff --git a/apollo-portal/pom.xml b/apollo-portal/pom.xml
index 68b59fe78..644c0ae7c 100644
--- a/apollo-portal/pom.xml
+++ b/apollo-portal/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.11.0</version>
+		<version>1.0.0</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
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 0c9448882..000000000
--- 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 d9bc956ee..d9831731b 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 <sxyx2008@163.com>
 
-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 84ba65d5d..878ba58a2 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 73ddce862..6c29b417a 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<String> 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/pom.xml b/pom.xml
index a104e4781..39ef45e0b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,11 +5,11 @@
 
 	<groupId>com.ctrip.framework.apollo</groupId>
 	<artifactId>apollo</artifactId>
-	<version>0.11.0</version>
+	<version>1.0.0</version>
 	<name>Apollo</name>
 	<packaging>pom</packaging>
 	<description>Ctrip Configuration Center</description>
-	<url>https://ctripcorp.github.io/apollo</url>
+	<url>https://github.com/ctripcorp/apollo</url>
 
 	<organization>
 		<name>Ctrip, Inc.</name>
@@ -85,6 +85,8 @@
 		<maven-war-plugin.version>3.0.0</maven-war-plugin.version>
 		<maven-install-plugin.version>2.5.2</maven-install-plugin.version>
 		<maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version>
+		<maven-javadoc-plugin.version>3.0.1</maven-javadoc-plugin.version>
+		<maven-gpg-plugin.version>1.6</maven-gpg-plugin.version>
 		<!-- for travis usage -->
 		<github.global.server>github</github.global.server>
 		<github.global.oauth2Token>${env.GITHUB_OAUTH_TOKEN}</github.global.oauth2Token>
@@ -348,6 +350,30 @@
 					<artifactId>maven-jar-plugin</artifactId>
 					<version>${maven-jar-plugin.version}</version>
 				</plugin>
+				<plugin>
+					<artifactId>maven-javadoc-plugin</artifactId>
+					<version>${maven-javadoc-plugin.version}</version>
+					<executions>
+						<execution>
+							<id>attach-javadoc</id>
+							<goals>
+								<goal>jar</goal>
+							</goals>
+							<configuration>
+								<doclint>none</doclint>
+							</configuration>
+						</execution>
+					</executions>
+					<configuration>
+						<show>public</show>
+						<charset>UTF-8</charset>
+						<encoding>UTF-8</encoding>
+						<docencoding>UTF-8</docencoding>
+						<links>
+							<link>http://docs.oracle.com/javase/7/docs/api</link>
+						</links>
+					</configuration>
+				</plugin>
 
 				<plugin>
 					<groupId>org.apache.maven.plugins</groupId>
@@ -366,6 +392,20 @@
 					<artifactId>maven-deploy-plugin</artifactId>
 					<version>${maven-deploy-plugin.version}</version>
 				</plugin>
+
+				<plugin>
+					<groupId>org.apache.maven.plugins</groupId>
+					<artifactId>maven-gpg-plugin</artifactId>
+					<version>${maven-gpg-plugin.version}</version>
+					<executions>
+						<execution>
+							<phase>verify</phase>
+							<goals>
+								<goal>sign</goal>
+							</goals>
+						</execution>
+					</executions>
+				</plugin>
 				<plugin>
 					<groupId>org.springframework.boot</groupId>
 					<artifactId>spring-boot-maven-plugin</artifactId>
@@ -378,29 +418,6 @@
 						</execution>
 					</executions>
 				</plugin>
-				<plugin>
-					<groupId>org.apache.maven.plugins</groupId>
-					<artifactId>maven-checkstyle-plugin</artifactId>
-					<version>2.17</version>
-					<dependencies>
-						<dependency>
-							<groupId>com.puppycrawl.tools</groupId>
-							<artifactId>checkstyle</artifactId>
-							<version>6.18</version>
-						</dependency>
-						<dependency>
-							<groupId>com.ctrip.framework.apollo</groupId>
-							<artifactId>apollo-buildtools</artifactId>
-							<version>${project.version}</version>
-						</dependency>
-					</dependencies>
-					<configuration>
-						<configLocation>google_checks.xml</configLocation>
-						<headerLocation>LICENSE-2.0.txt</headerLocation>
-						<failOnViolation>false</failOnViolation>
-						<consoleOutput>true</consoleOutput>
-					</configuration>
-				</plugin>
 				<plugin>
 					<groupId>org.codehaus.mojo</groupId>
 					<artifactId>findbugs-maven-plugin</artifactId>
@@ -462,10 +479,6 @@
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-deploy-plugin</artifactId>
 			</plugin>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-checkstyle-plugin</artifactId>
-			</plugin>
 			<plugin>
 				<groupId>org.codehaus.mojo</groupId>
 				<artifactId>findbugs-maven-plugin</artifactId>
@@ -531,7 +544,6 @@
 				<filtering>true</filtering>
 				<includes>
 					<include>application-github.properties</include>
-					<include>apollo-env.properties</include>
 				</includes>
 			</resource>
 			<resource>
@@ -539,7 +551,6 @@
 				<filtering>false</filtering>
 				<excludes>
 					<exclude>application-github.properties</exclude>
-					<exclude>apollo-env.properties</exclude>
 				</excludes>
 			</resource>
 		</resources>
@@ -815,6 +826,21 @@
 				</dependency>
 			</dependencies>
 		</profile>
+		<profile>
+			<id>release</id>
+			<build>
+				<plugins>
+					<plugin>
+						<groupId>org.apache.maven.plugins</groupId>
+						<artifactId>maven-javadoc-plugin</artifactId>
+					</plugin>
+					<plugin>
+						<groupId>org.apache.maven.plugins</groupId>
+						<artifactId>maven-gpg-plugin</artifactId>
+					</plugin>
+				</plugins>
+			</build>
+		</profile>
 	</profiles>
 
 	<reporting>
diff --git a/scripts/build.bat b/scripts/build.bat
index 290eced0d..b28dfcfe7 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 9f3e2a99f..cb6a539ba 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 ===="
-


Test patch:
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 19b7ab75c..d86f092e3 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<String, ConfigChange> changes = Maps.newHashMap();
+    changes.put(someKeyChanged, mock(ConfigChange.class));
+    changes.put(anotherKeyChanged, mock(ConfigChange.class));
+    ConfigChangeEvent someChangeEvent = new ConfigChangeEvent(someNamespace, changes);
+
+    final SettableFuture<ConfigChangeEvent> interestedInAllKeysFuture = SettableFuture.create();
+    ConfigChangeListener interestedInAllKeys = new ConfigChangeListener() {
+      @Override
+      public void onChange(ConfigChangeEvent changeEvent) {
+        interestedInAllKeysFuture.set(changeEvent);
+      }
+    };
+
+    final SettableFuture<ConfigChangeEvent> interestedInSomeKeyFuture = SettableFuture.create();
+    ConfigChangeListener interestedInSomeKey = new ConfigChangeListener() {
+      @Override
+      public void onChange(ConfigChangeEvent changeEvent) {
+        interestedInSomeKeyFuture.set(changeEvent);
+      }
+    };
+
+    final SettableFuture<ConfigChangeEvent> 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 000000000..f84075f6d
--- /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 c278eb92e..09c452ad8 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<Set> applicationConfigInterestedKeys = ArgumentCaptor.forClass(Set.class);
+    final ArgumentCaptor<Set> 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<String> 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> T getBean(Class<T> 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 a58c0a8de..0010a00de 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<Set> applicationConfigInterestedKeys = ArgumentCaptor.forClass(Set.class);
+    final ArgumentCaptor<Set> 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<String> 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> T getBean(String xmlLocation, Class<T> 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 000000000..ac929482c
--- /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 000000000..bd7883753
--- /dev/null
+++ b/apollo-client/src/test/resources/spring/XmlConfigAnnotationTest6.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:apollo="http://www.ctrip.com/schema/apollo"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+       http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">
+    <apollo:config/>
+
+    <bean class="com.ctrip.framework.apollo.spring.XMLConfigAnnotationTest.TestApolloConfigChangeListenerWithInterestedKeysBean"/>
+</beans>
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 bc9471385..67080420c 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<Env, String> 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 000000000..aeac65753
--- /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 000000000..4445e6a3d
--- /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 81313ef25..b3b8ed617 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 000000000..bd6d23f18
--- /dev/null
+++ b/apollo-core/src/test/resources/META-INF/services/com.ctrip.framework.apollo.core.spi.MetaServerProvider
@@ -0,0 +1,1 @@
+com.ctrip.framework.apollo.core.MetaDomainTest$MockMetaServerProvider