Merge branch 'develop' into service-test
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
new file mode 100755
index 0000000..01e6799
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.jar
Binary files differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100755
index 0000000..b6e6781
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 259bf59..414bb88 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,8 +4,15 @@
 jdk:
     - oraclejdk8
     - oraclejdk11
+
+cache:
+  directories:
+    - $HOME/.m2
+
+install: true
+
 script:
-    - travis_wait 30 mvn clean package
+    - travis_wait 30 ./mvnw clean install -DskipTests=false -Dcheckstyle.skip=false -Dmaven.javadoc.skip=true
 
 after_success:
   - bash <(curl -s https://codecov.io/bash)
diff --git a/README_ZH.md b/README_ZH.md
index 625acfc..0790ee0 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -24,7 +24,6 @@
 * 标准spring boot工程
 * **注意** 本分支依赖Dubbo2.7-SNAPSHOT版本,该Dubbo版本还未正式发布,因此如果发现依赖方面的错误,请清空本地库中的dubbo2.7相关文件
 * [application.properties配置说明](https://github.com/apache/incubator-dubbo-ops/wiki/Dubbo-Admin%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E)  
-* **注意** 本分支依赖Dubbo2.7-SNAPSHOT版本,该Dubbo版本还未正式发布,因此如果发现依赖方面的错误,请清空本地库中的dubbo2.7相关文件
 * 在项目根目录(incubator-dubbo-ops)第一次构建需要强制更新: `mvn -Dmaven.test.skip=true clean -U package`
 
 
diff --git a/codestyle/checkstyle.xml b/codestyle/checkstyle.xml
new file mode 100644
index 0000000..9c7581b
--- /dev/null
+++ b/codestyle/checkstyle.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<!DOCTYPE module PUBLIC
+        "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
+        "http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd">
+
+<module name="Checker">
+    <property name="charset" value="UTF-8"/>
+    <property name="fileExtensions" value="java"/>
+
+    <!-- TreeWalker Checks -->
+    <module name="TreeWalker">
+        <module name="SuppressWarningsHolder" />
+
+        <module name="AvoidStarImport"/>
+        <module name="AvoidEscapedUnicodeCharacters">
+            <property name="allowEscapesForControlCharacters" value="true"/>
+            <property name="allowByTailComment" value="true"/>
+            <property name="allowNonPrintableEscapes" value="true"/>
+        </module>
+        <module name="NoLineWrap"/>
+        <module name="OuterTypeFilename"/>
+        <module name="UnusedImports"/>
+
+    </module>
+</module>
diff --git a/codestyle/dubbo_codestyle_for_idea.xml b/codestyle/dubbo_codestyle_for_idea.xml
new file mode 100644
index 0000000..1cbbe37
--- /dev/null
+++ b/codestyle/dubbo_codestyle_for_idea.xml
@@ -0,0 +1,33 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<code_scheme name="dubbo_codestyle">
+    <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99"/>
+    <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99"/>
+    <option name="IMPORT_LAYOUT_TABLE">
+        <value>
+            <package name="org.apache.dubbo.admin" withSubpackages="true" static="false"/>
+            <emptyLine/>
+            <package name="" withSubpackages="true" static="false"/>
+            <emptyLine/>
+            <package name="javax" withSubpackages="true" static="false"/>
+            <package name="java" withSubpackages="true" static="false"/>
+            <emptyLine/>
+            <package name="" withSubpackages="true" static="true"/>
+        </value>
+    </option>
+</code_scheme>
diff --git a/dubbo-admin-backend/pom.xml b/dubbo-admin-backend/pom.xml
index dd10133..c958c30 100644
--- a/dubbo-admin-backend/pom.xml
+++ b/dubbo-admin-backend/pom.xml
@@ -32,6 +32,7 @@
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <java.version>1.8</java.version>
+        <mockito-version>2.23.4</mockito-version>
     </properties>
 
     <dependencies>
@@ -123,6 +124,12 @@
             <groupId>io.netty</groupId>
             <artifactId>netty-all</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>${mockito-version}</version>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/Constants.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/Constants.java
index ef2ef89..6bd38ca 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/Constants.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/Constants.java
@@ -54,6 +54,7 @@
     public static final String CONSUMERS_CATEGORY = "consumers";
     public static final String SPECIFICATION_VERSION_KEY = "release";
     public static final String GLOBAL_CONFIG = "global";
+    public static final String GLOBAL_CONFIG_PATH = "config/dubbo/dubbo.properties";
     public static final Set<String> CONFIGS = new HashSet<>();
 
     static {
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/OverrideUtils.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/OverrideUtils.java
index cdad836..fb18d56 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/OverrideUtils.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/OverrideUtils.java
@@ -16,8 +16,9 @@
  */
 package org.apache.dubbo.admin.common.util;
 
-import org.apache.dubbo.admin.model.domain.*;
+import org.apache.dubbo.admin.model.domain.LoadBalance;
 import org.apache.dubbo.admin.model.domain.Override;
+import org.apache.dubbo.admin.model.domain.Weight;
 import org.apache.dubbo.admin.model.dto.BalancingDTO;
 import org.apache.dubbo.admin.model.dto.DynamicConfigDTO;
 import org.apache.dubbo.admin.model.dto.WeightDTO;
@@ -25,7 +26,11 @@
 import org.apache.dubbo.admin.model.store.OverrideDTO;
 import org.apache.dubbo.common.utils.StringUtils;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * OverrideUtils.java
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/ParseUtils.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/ParseUtils.java
index 13035db..5a2b55d 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/ParseUtils.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/ParseUtils.java
@@ -18,7 +18,13 @@
 
 import org.apache.dubbo.common.utils.StringUtils;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.regex.Matcher;
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/RouteUtils.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/RouteUtils.java
index cb2c759..81b505c 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/RouteUtils.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/RouteUtils.java
@@ -20,14 +20,20 @@
 import org.apache.dubbo.admin.model.dto.AccessDTO;
 import org.apache.dubbo.admin.model.dto.ConditionRouteDTO;
 import org.apache.dubbo.admin.model.dto.TagRouteDTO;
-import org.apache.dubbo.admin.model.store.BlackWhiteList;
 import org.apache.dubbo.admin.model.store.RoutingRule;
 import org.apache.dubbo.admin.model.store.TagRoute;
 import org.apache.dubbo.common.utils.StringUtils;
 
 import java.text.ParseException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/config/ConfigCenter.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/config/ConfigCenter.java
index 382e2b2..55be526 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/config/ConfigCenter.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/config/ConfigCenter.java
@@ -23,14 +23,12 @@
 import org.apache.dubbo.admin.registry.config.GovernanceConfiguration;
 import org.apache.dubbo.admin.registry.metadata.MetaDataCollector;
 import org.apache.dubbo.admin.registry.metadata.impl.NoOpMetadataCollector;
-import org.apache.dubbo.admin.service.ManagementService;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.RegistryFactory;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/AccessesController.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/AccessesController.java
index ac350c5..055b48c 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/AccessesController.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/AccessesController.java
@@ -16,23 +16,31 @@
  */
 package org.apache.dubbo.admin.controller;
 
-import org.apache.dubbo.admin.common.exception.VersionValidationException;
-import org.apache.dubbo.admin.common.util.Constants;
-import org.apache.dubbo.admin.model.dto.ConditionRouteDTO;
-import org.apache.dubbo.admin.service.ProviderService;
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.dubbo.admin.common.exception.ParamValidationException;
 import org.apache.dubbo.admin.common.exception.ResourceNotFoundException;
+import org.apache.dubbo.admin.common.exception.VersionValidationException;
+import org.apache.dubbo.admin.common.util.Constants;
 import org.apache.dubbo.admin.model.dto.AccessDTO;
+import org.apache.dubbo.admin.model.dto.ConditionRouteDTO;
+import org.apache.dubbo.admin.service.ProviderService;
 import org.apache.dubbo.admin.service.RouteService;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
 
 import java.text.ParseException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
 
 @RestController
 @RequestMapping("/api/{env}/rules/access")
@@ -89,7 +97,7 @@
             throw new ParamValidationException("Either Service or application is required.");
         }
         String application = accessDTO.getApplication();
-        if (StringUtils.isNotEmpty(application) && this.providerService.findVersionInApplication(application).equals("2.6")) {
+        if (StringUtils.isNotEmpty(application) && "2.6".equals(providerService.findVersionInApplication(application))) {
             throw new VersionValidationException("dubbo 2.6 does not support application scope blackwhite list config");
         }
         if (accessDTO.getBlacklist() == null && accessDTO.getWhitelist() == null) {
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ConditionRoutesController.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ConditionRoutesController.java
index f764801..cd1b894 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ConditionRoutesController.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ConditionRoutesController.java
@@ -27,7 +27,13 @@
 import org.apache.dubbo.admin.service.RouteService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/LoadBalanceController.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/LoadBalanceController.java
index a803527..b314aed 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/LoadBalanceController.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/LoadBalanceController.java
@@ -27,7 +27,13 @@
 import org.apache.dubbo.admin.service.ProviderService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ManagementController.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ManagementController.java
index c846ce6..b12c689 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ManagementController.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ManagementController.java
@@ -25,7 +25,12 @@
 import org.apache.dubbo.admin.service.ProviderService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -72,7 +77,7 @@
         List<ConfigDTO> configDTOs = new ArrayList<>();
         if (key.equals(Constants.ANY_VALUE)) {
             query = providerService.findApplications();
-            query.add("global");
+            query.add(Constants.GLOBAL_CONFIG);
         } else {
             query.add(key);
         }
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/OverridesController.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/OverridesController.java
index fb19178..82ba032 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/OverridesController.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/OverridesController.java
@@ -27,7 +27,13 @@
 import org.apache.dubbo.admin.service.ProviderService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ServiceController.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ServiceController.java
index dfbc607..ae9069f 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ServiceController.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ServiceController.java
@@ -17,8 +17,8 @@
 
 package org.apache.dubbo.admin.controller;
 
-import com.ctrip.framework.apollo.core.enums.Env;
 import com.google.gson.Gson;
+import org.apache.dubbo.admin.common.util.Constants;
 import org.apache.dubbo.admin.common.util.ConvertUtil;
 import org.apache.dubbo.admin.model.domain.Consumer;
 import org.apache.dubbo.admin.model.domain.Provider;
@@ -26,16 +26,18 @@
 import org.apache.dubbo.admin.model.dto.ServiceDetailDTO;
 import org.apache.dubbo.admin.service.ConsumerService;
 import org.apache.dubbo.admin.service.ProviderService;
-import org.apache.dubbo.admin.common.util.Constants;
 import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
 import org.apache.dubbo.metadata.identifier.MetadataIdentifier;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
 
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 @RestController
 @RequestMapping("/api/{env}")
@@ -43,11 +45,13 @@
 
     private final ProviderService providerService;
     private final ConsumerService consumerService;
+    private final Gson gson;
 
     @Autowired
     public ServiceController(ProviderService providerService, ConsumerService consumerService) {
         this.providerService = providerService;
         this.consumerService = consumerService;
+        this.gson = new Gson();
     }
 
     @RequestMapping( value = "/service", method = RequestMethod.GET)
@@ -56,8 +60,6 @@
         return providerService.getServiceDTOS(pattern, filter, env);
     }
 
-
-
     @RequestMapping(value = "/service/{service}", method = RequestMethod.GET)
     public ServiceDetailDTO serviceDetail(@PathVariable String service, @PathVariable String env) {
         service = service.replace(Constants.ANY_VALUE, Constants.PATH_SEPARATOR);
@@ -78,7 +80,6 @@
         serviceDetailDTO.setConsumers(consumers);
         serviceDetailDTO.setProviders(providers);
         if (metadata != null) {
-            Gson gson = new Gson();
             FullServiceDefinition serviceDefinition = gson.fromJson(metadata, FullServiceDefinition.class);
             serviceDetailDTO.setMetadata(serviceDefinition);
         }
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/TagRoutesController.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/TagRoutesController.java
index e097d48..521c9d2 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/TagRoutesController.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/TagRoutesController.java
@@ -27,7 +27,13 @@
 import org.apache.dubbo.admin.service.RouteService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/WeightController.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/WeightController.java
index 2146a01..3333e90 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/WeightController.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/WeightController.java
@@ -22,17 +22,18 @@
 import org.apache.dubbo.admin.common.exception.ResourceNotFoundException;
 import org.apache.dubbo.admin.common.exception.VersionValidationException;
 import org.apache.dubbo.admin.common.util.Constants;
-import org.apache.dubbo.admin.common.util.ConvertUtil;
-import org.apache.dubbo.admin.model.dto.AccessDTO;
 import org.apache.dubbo.admin.model.dto.WeightDTO;
 import org.apache.dubbo.admin.service.OverrideService;
-import org.apache.dubbo.admin.model.domain.Override;
-import org.apache.dubbo.admin.model.domain.Weight;
-import org.apache.dubbo.admin.common.util.OverrideUtils;
 import org.apache.dubbo.admin.service.ProviderService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/AccessDTO.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/AccessDTO.java
index 8b8e7b4..e66f3ba 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/AccessDTO.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/AccessDTO.java
@@ -16,7 +16,6 @@
  */
 package org.apache.dubbo.admin.model.dto;
 
-import java.util.List;
 import java.util.Set;
 
 public class AccessDTO extends BaseDTO {
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/ServiceDTO.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/ServiceDTO.java
index 3822291..0202526 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/ServiceDTO.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/ServiceDTO.java
@@ -17,9 +17,10 @@
 
 package org.apache.dubbo.admin.model.dto;
 
-import java.util.Objects;
 import org.apache.commons.lang3.StringUtils;
 
+import java.util.Objects;
+
 public class ServiceDTO implements Comparable<ServiceDTO>{
     private String service;
     private String appName;
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/registry/config/impl/ZookeeperConfiguration.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/registry/config/impl/ZookeeperConfiguration.java
index 1eed438..362dd26 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/registry/config/impl/ZookeeperConfiguration.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/registry/config/impl/ZookeeperConfiguration.java
@@ -17,17 +17,16 @@
 
 package org.apache.dubbo.admin.registry.config.impl;
 
+import org.apache.commons.lang3.StringUtils;
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.retry.ExponentialBackoffRetry;
 import org.apache.dubbo.admin.common.util.Constants;
 import org.apache.dubbo.admin.registry.config.GovernanceConfiguration;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 
-import org.apache.commons.lang3.StringUtils;
-import org.apache.curator.framework.CuratorFramework;
-import org.apache.curator.framework.CuratorFrameworkFactory;
-import org.apache.curator.retry.ExponentialBackoffRetry;
-
 public class ZookeeperConfiguration implements GovernanceConfiguration {
     private static final Logger logger = LoggerFactory.getLogger(ZookeeperConfiguration.class);
     private CuratorFramework zkClient;
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/RegistryServerSync.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/RegistryServerSync.java
index 1cd7bb5..6b28f02 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/RegistryServerSync.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/RegistryServerSync.java
@@ -31,7 +31,11 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import java.util.*;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicLong;
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/ManagementServiceImpl.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/ManagementServiceImpl.java
index d43e6de..a1668fd 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/ManagementServiceImpl.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/ManagementServiceImpl.java
@@ -22,16 +22,14 @@
 import org.apache.dubbo.admin.service.ManagementService;
 import org.springframework.stereotype.Component;
 
+import static org.apache.dubbo.admin.common.util.Constants.GLOBAL_CONFIG_PATH;
+
 @Component
 public class ManagementServiceImpl extends AbstractService implements ManagementService {
-
-
-    private static String globalConfigPath = "config/dubbo/dubbo.properties";
-
     @Override
     public void setConfig(ConfigDTO config) {
         if (Constants.GLOBAL_CONFIG.equals(config.getKey())) {
-            dynamicConfiguration.setConfig(globalConfigPath, config.getConfig());
+            dynamicConfiguration.setConfig(GLOBAL_CONFIG_PATH, config.getConfig());
         } else {
             dynamicConfiguration.setConfig(getPath(config.getKey()), config.getConfig());
         }
@@ -40,7 +38,7 @@
     @Override
     public String getConfig(String key) {
         if (Constants.GLOBAL_CONFIG.equals(key)) {
-            return dynamicConfiguration.getConfig(globalConfigPath);
+            return dynamicConfiguration.getConfig(GLOBAL_CONFIG_PATH);
         }
         return dynamicConfiguration.getConfig(getPath(key));
     }
@@ -48,7 +46,7 @@
     @Override
     public String getConfigPath(String key) {
         if (Constants.GLOBAL_CONFIG.equals(key)) {
-            return dynamicConfiguration.getPath(globalConfigPath);
+            return dynamicConfiguration.getPath(GLOBAL_CONFIG_PATH);
         }
         return dynamicConfiguration.getPath(getPath(key));
     }
@@ -57,7 +55,7 @@
     public boolean updateConfig(ConfigDTO configDTO) {
         String key = configDTO.getKey();
         if (Constants.GLOBAL_CONFIG.equals(key)) {
-            dynamicConfiguration.setConfig(globalConfigPath, configDTO.getConfig());
+            dynamicConfiguration.setConfig(GLOBAL_CONFIG_PATH, configDTO.getConfig());
         } else {
             dynamicConfiguration.setConfig(getPath(key), configDTO.getConfig());
         }
@@ -67,7 +65,7 @@
     @Override
     public boolean deleteConfig(String key) {
         if (Constants.GLOBAL_CONFIG.equals(key)) {
-            dynamicConfiguration.deleteConfig(globalConfigPath);
+            dynamicConfiguration.deleteConfig(GLOBAL_CONFIG_PATH);
         } else {
             dynamicConfiguration.deleteConfig(getPath(key));
         }
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/ProviderServiceImpl.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/ProviderServiceImpl.java
index 98e3e63..10a4fe4 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/ProviderServiceImpl.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/ProviderServiceImpl.java
@@ -17,6 +17,7 @@
 package org.apache.dubbo.admin.service.impl;
 
 import org.apache.dubbo.admin.common.exception.ParamValidationException;
+import org.apache.dubbo.admin.common.util.Constants;
 import org.apache.dubbo.admin.common.util.Pair;
 import org.apache.dubbo.admin.common.util.ParseUtils;
 import org.apache.dubbo.admin.common.util.SyncUtils;
@@ -24,15 +25,21 @@
 import org.apache.dubbo.admin.model.dto.ServiceDTO;
 import org.apache.dubbo.admin.service.OverrideService;
 import org.apache.dubbo.admin.service.ProviderService;
-import org.apache.dubbo.admin.common.util.Constants;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.metadata.identifier.MetadataIdentifier;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.ConcurrentMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
diff --git a/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/AbstractSpringIntegrationTest.java b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/AbstractSpringIntegrationTest.java
index a151d3b..5640c30 100644
--- a/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/AbstractSpringIntegrationTest.java
+++ b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/AbstractSpringIntegrationTest.java
@@ -17,7 +17,6 @@
 
 package org.apache.dubbo.admin;
 
-import java.io.IOException;
 import org.apache.curator.framework.CuratorFramework;
 import org.apache.curator.framework.CuratorFrameworkFactory;
 import org.apache.curator.retry.RetryOneTime;
@@ -29,11 +28,13 @@
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.web.client.TestRestTemplate;
 import org.springframework.test.context.ActiveProfiles;
-import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.web.client.RestTemplate;
 
+import java.io.IOException;
+
 @ActiveProfiles("test")
-@RunWith(SpringRunner.class)
+@RunWith(SpringJUnit4ClassRunner.class)
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
 public abstract class AbstractSpringIntegrationTest {
   protected RestTemplate restTemplate = (new TestRestTemplate()).getRestTemplate();
diff --git a/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/common/util/CoderUtilTest.java b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/common/util/CoderUtilTest.java
new file mode 100644
index 0000000..7c9daf5
--- /dev/null
+++ b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/common/util/CoderUtilTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.admin.common.util;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Java6Assertions.fail;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class CoderUtilTest {
+
+    @Test
+    public void MD5_16bit() {
+        assertNull(CoderUtil.MD5_16bit(null));
+
+        String input = "dubbo";
+        String output = "2CC9DEED96FE012E";
+        assertEquals(output, CoderUtil.MD5_16bit(input));
+    }
+
+    @Test
+    public void MD5_32bit() {
+        String input = null;
+        assertNull(CoderUtil.MD5_32bit(input));
+
+        input = "dubbo";
+        String output = "AA4E1B8C2CC9DEED96FE012EF2E0752A";
+        assertEquals(output, CoderUtil.MD5_32bit(input));
+    }
+
+    @Test
+    public void MD5_32bit1() {
+        byte[] input = null;
+        assertNull(CoderUtil.MD5_32bit(input));
+
+        input = "dubbo".getBytes();
+        String output = "AA4E1B8C2CC9DEED96FE012EF2E0752A";
+        assertEquals(output, CoderUtil.MD5_32bit(input));
+    }
+
+    @Test
+    public void decodeBase64() {
+        try {
+            CoderUtil.decodeBase64(null);
+            fail("when param is null, this should throw exception");
+        } catch (Exception e) {
+        }
+
+        String input = "ZHViYm8=";
+        String output = "dubbo";
+        assertEquals(output, CoderUtil.decodeBase64(input));
+    }
+}
\ No newline at end of file
diff --git a/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/config/ConfigCenterTest.java b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/config/ConfigCenterTest.java
index 1be2ba9..cb81fa7 100644
--- a/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/config/ConfigCenterTest.java
+++ b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/config/ConfigCenterTest.java
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package org.apache.dubbo.admin.config;
 
 import org.apache.curator.framework.CuratorFramework;
diff --git a/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/controller/AccessesControllerTest.java b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/controller/AccessesControllerTest.java
new file mode 100644
index 0000000..fc494ba
--- /dev/null
+++ b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/controller/AccessesControllerTest.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.admin.controller;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.dubbo.admin.AbstractSpringIntegrationTest;
+import org.apache.dubbo.admin.model.dto.AccessDTO;
+import org.apache.dubbo.admin.model.dto.ConditionRouteDTO;
+import org.apache.dubbo.admin.service.ProviderService;
+import org.apache.dubbo.admin.service.RouteService;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.ResponseEntity;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class AccessesControllerTest extends AbstractSpringIntegrationTest {
+
+    private final String env = "whatever";
+
+    @MockBean
+    private RouteService routeService;
+    @MockBean
+    private ProviderService providerService;
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    @Test
+    public void searchAccess() throws IOException {
+        AccessDTO accessDTO = new AccessDTO();
+        ResponseEntity<String> response;
+        String exceptResponseBody;
+        // application and service is all empty
+        response = restTemplate.getForEntity(url("/api/{env}/rules/access"), String.class, env);
+        Map map = objectMapper.readValue(response.getBody(), Map.class);
+        assertFalse("should return a fail response", (Boolean) map.get("success"));
+
+        // when application is present
+        String application = "applicationName";
+        when(routeService.findAccess(application)).thenReturn(accessDTO);
+        response = restTemplate.getForEntity(url("/api/{env}/rules/access?application={application}"), String.class, env, application);
+        exceptResponseBody = objectMapper.writeValueAsString(Collections.singletonList(accessDTO));
+        assertEquals(exceptResponseBody, response.getBody());
+
+        // when service is present
+        String service = "serviceName";
+        when(routeService.findAccess(service)).thenReturn(accessDTO);
+        response = restTemplate.getForEntity(url("/api/{env}/rules/access?service={service}"), String.class, env, service);
+        exceptResponseBody = objectMapper.writeValueAsString(Collections.singletonList(accessDTO));
+        assertEquals(exceptResponseBody, response.getBody());
+    }
+
+    @Test
+    public void detailAccess() throws JsonProcessingException {
+        String id = "1";
+        AccessDTO accessDTO = new AccessDTO();
+        when(routeService.findAccess(id)).thenReturn(accessDTO);
+        ResponseEntity<String> response = restTemplate.getForEntity(url("/api/{env}/rules/access/{id}"), String.class, env, id);
+        String exceptResponseBody = objectMapper.writeValueAsString(accessDTO);
+        assertEquals(exceptResponseBody, response.getBody());
+    }
+
+    @Test
+    public void deleteAccess() {
+        String id = "1";
+        restTemplate.delete(url("/api/{env}/rules/access/{id}"), env, id);
+        verify(routeService).deleteAccess(id);
+    }
+
+    @Test
+    public void createAccess() {
+        AccessDTO accessDTO = new AccessDTO();
+        String application = "applicationName";
+
+        restTemplate.postForLocation(url("/api/{env}/rules/access"), accessDTO, env);
+        // when application is present & dubbo's version is 2.6
+        accessDTO.setApplication(application);
+        when(providerService.findVersionInApplication(application)).thenReturn("2.6");
+        restTemplate.postForLocation(url("/api/{env}/rules/access"), accessDTO, env);
+        // dubbo's version is 2.7
+        when(providerService.findVersionInApplication(application)).thenReturn("2.7");
+        restTemplate.postForLocation(url("/api/{env}/rules/access"), accessDTO, env);
+        // black white list is not null
+        accessDTO.setBlacklist(new HashSet<>());
+        accessDTO.setWhitelist(new HashSet<>());
+        restTemplate.postForLocation(url("/api/{env}/rules/access"), accessDTO, env);
+        verify(routeService).createAccess(any(AccessDTO.class));
+    }
+
+    @Test
+    public void updateAccess() throws IOException {
+        AccessDTO accessDTO = new AccessDTO();
+        String id = "1";
+        // when id is 'Unknown ID'
+        restTemplate.put(url("/api/{env}/rules/access/{id}"), accessDTO, env, id);
+        verify(routeService).findConditionRoute(id);
+        //
+        ConditionRouteDTO conditionRouteDTO = mock(ConditionRouteDTO.class);
+        when(routeService.findConditionRoute(id)).thenReturn(conditionRouteDTO);
+        restTemplate.put(url("/api/{env}/rules/access/{id}"), accessDTO, env, id);
+        verify(routeService).updateAccess(any(AccessDTO.class));
+    }
+}
\ No newline at end of file
diff --git a/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/controller/ConditionRoutesControllerTest.java b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/controller/ConditionRoutesControllerTest.java
new file mode 100644
index 0000000..27f2962
--- /dev/null
+++ b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/controller/ConditionRoutesControllerTest.java
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.admin.controller;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import org.apache.dubbo.admin.AbstractSpringIntegrationTest;
+import org.apache.dubbo.admin.common.util.YamlParser;
+import org.apache.dubbo.admin.model.dto.ConditionRouteDTO;
+import org.apache.dubbo.admin.model.store.RoutingRule;
+import org.apache.dubbo.admin.service.ProviderService;
+import org.junit.After;
+import org.junit.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+public class ConditionRoutesControllerTest extends AbstractSpringIntegrationTest {
+  private final String env = "whatever";
+
+  @MockBean
+  private ProviderService providerService;
+
+  @After
+  public void tearDown() throws Exception {
+    if (zkClient.checkExists().forPath("/dubbo") != null) {
+      zkClient.delete().deletingChildrenIfNeeded().forPath("/dubbo");
+    }
+  }
+
+  @Test
+  public void shouldThrowWhenParamInvalid() {
+    String uuid = UUID.randomUUID().toString();
+
+    ConditionRouteDTO dto = new ConditionRouteDTO();
+    ResponseEntity<String> responseEntity = restTemplate.postForEntity(
+        url("/api/{env}/rules/route/condition"), dto, String.class, env
+    );
+    assertThat(responseEntity.getStatusCode(), is(HttpStatus.BAD_REQUEST));
+    assertThat(responseEntity.getBody(), containsString("serviceName and app is Empty!"));
+
+    dto.setApplication("application" + uuid);
+    when(providerService.findVersionInApplication(dto.getApplication())).thenReturn("2.6");
+    responseEntity = restTemplate.postForEntity(
+        url("/api/{env}/rules/route/condition"), dto, String.class, env
+    );
+    assertThat(responseEntity.getStatusCode(), is(HttpStatus.INTERNAL_SERVER_ERROR));
+    assertThat(responseEntity.getBody(), containsString("dubbo 2.6 does not support application scope routing rule"));
+  }
+
+  @Test
+  public void shouldCreateRule() {
+    String uuid = UUID.randomUUID().toString();
+    String application = "application" + uuid;
+    String service = "service" + uuid;
+    List<String> conditions = Collections.singletonList("=> host != 172.22.3.91");
+
+    ConditionRouteDTO dto = new ConditionRouteDTO();
+    dto.setService(service);
+    dto.setConditions(conditions);
+
+    ResponseEntity<String> responseEntity = restTemplate.postForEntity(
+        url("/api/{env}/rules/route/condition"), dto, String.class, env
+    );
+    assertThat(responseEntity.getStatusCode(), is(HttpStatus.CREATED));
+
+    dto.setApplication(application);
+    when(providerService.findVersionInApplication(dto.getApplication())).thenReturn("2.7");
+
+    responseEntity = restTemplate.postForEntity(
+        url("/api/{env}/rules/route/condition"), dto, String.class, env
+    );
+    assertThat(responseEntity.getStatusCode(), is(HttpStatus.CREATED));
+  }
+
+  @Test
+  public void shouldUpdateRule() throws Exception {
+    String service = "org.apache.dubbo.demo.DemoService";
+    String content = "conditions:\n"
+        + "- => host != 172.22.3.111\n"
+        + "- => host != 172.22.3.112\n"
+        + "enabled: true\n"
+        + "force: true\n"
+        + "key: " + service + "\n"
+        + "priority: 0\n"
+        + "runtime: false\n"
+        + "scope: service";
+    String path = "/dubbo/config/" + service + "/condition-router";
+    zkClient.create().creatingParentContainersIfNeeded().forPath(path);
+    zkClient.setData().forPath(path, content.getBytes());
+
+    List<String> newConditions = Arrays.asList("=> host != 172.22.3.211", "=> host != 172.22.3.212");
+
+    ConditionRouteDTO dto = new ConditionRouteDTO();
+    dto.setConditions(newConditions);
+    dto.setService(service);
+
+    ResponseEntity<String> responseEntity = restTemplate.exchange(
+        url("/api/{env}/rules/route/condition/{service}"), HttpMethod.PUT,
+        new HttpEntity<>(dto, null), String.class, env, service
+    );
+    assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK));
+
+    byte[] bytes = zkClient.getData().forPath(path);
+    String updatedConfig = new String(bytes);
+    RoutingRule rule = YamlParser.loadObject(updatedConfig, RoutingRule.class);
+    assertThat(rule.getConditions(), containsInAnyOrder(newConditions.toArray()));
+  }
+
+  @Test
+  public void shouldGetServiceRule() throws Exception {
+    String service = "org.apache.dubbo.demo.DemoService";
+    String content = "conditions:\n"
+        + "- => host != 172.22.3.111\n"
+        + "- => host != 172.22.3.112\n"
+        + "enabled: true\n"
+        + "force: true\n"
+        + "key: " + service + "\n"
+        + "priority: 0\n"
+        + "runtime: false\n"
+        + "scope: service";
+    String path = "/dubbo/config/" + service + "/condition-router";
+    zkClient.create().creatingParentContainersIfNeeded().forPath(path);
+    zkClient.setData().forPath(path, content.getBytes());
+
+    ResponseEntity<List<ConditionRouteDTO>> responseEntity = restTemplate.exchange(
+        url("/api/{env}/rules/route/condition/?service={service}"), HttpMethod.GET,
+        null, new ParameterizedTypeReference<List<ConditionRouteDTO>>() {
+        }, env, service
+    );
+    assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK));
+    assertThat(responseEntity.getBody(), hasSize(1));
+    List<String> conditions = responseEntity.getBody()
+        .stream()
+        .flatMap(it -> it.getConditions().stream())
+        .collect(Collectors.toList());
+    assertThat(conditions, hasSize(2));
+    assertThat(conditions, containsInAnyOrder("=> host != 172.22.3.111", "=> host != 172.22.3.112"));
+  }
+
+  @Test
+  public void shouldDeleteRule() throws Exception {
+    String service = "org.apache.dubbo.demo.DemoService";
+    String content = "conditions:\n"
+        + "- => host != 172.22.3.111\n"
+        + "- => host != 172.22.3.112\n"
+        + "enabled: true\n"
+        + "force: true\n"
+        + "key: " + service + "\n"
+        + "priority: 0\n"
+        + "runtime: false\n"
+        + "scope: service";
+    String path = "/dubbo/config/" + service + "/condition-router";
+    zkClient.create().creatingParentContainersIfNeeded().forPath(path);
+    zkClient.setData().forPath(path, content.getBytes());
+
+    assertNotNull("zk path should not be null before deleting", zkClient.checkExists().forPath(path));
+
+    ResponseEntity<String> responseEntity = restTemplate.exchange(
+        url("/api/{env}/rules/route/condition/{service}"), HttpMethod.DELETE,
+        null, String.class, env, service
+    );
+    assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK));
+
+    assertNull(zkClient.checkExists().forPath(path));
+  }
+
+  @Test
+  public void shouldThrowWhenDetailRouteWithUnknownId() {
+    ResponseEntity<String> responseEntity = restTemplate.getForEntity(
+        url("/api/{env}/rules/route/condition/{id}"), String.class, env, "non-existed-service"
+    );
+    assertThat(responseEntity.getStatusCode(), is(HttpStatus.NOT_FOUND));
+  }
+
+  @Test
+  public void shouldGetRouteDetail() throws Exception {
+    String service = "org.apache.dubbo.demo.DemoService";
+    String content = "conditions:\n"
+        + "- => host != 172.22.3.111\n"
+        + "- => host != 172.22.3.112\n"
+        + "enabled: true\n"
+        + "force: true\n"
+        + "key: " + service + "\n"
+        + "priority: 0\n"
+        + "runtime: false\n"
+        + "scope: service";
+    String path = "/dubbo/config/" + service + "/condition-router";
+    zkClient.create().creatingParentContainersIfNeeded().forPath(path);
+    zkClient.setData().forPath(path, content.getBytes());
+
+    ResponseEntity<ConditionRouteDTO> responseEntity = restTemplate.getForEntity(
+        url("/api/{env}/rules/route/condition/{id}"), ConditionRouteDTO.class, env, service
+    );
+    assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK));
+
+    ConditionRouteDTO conditionRouteDTO = responseEntity.getBody();
+    assertNotNull(conditionRouteDTO);
+    assertThat(conditionRouteDTO.getConditions(), hasSize(2));
+    assertThat(conditionRouteDTO.getConditions(), containsInAnyOrder("=> host != 172.22.3.111", "=> host != 172.22.3.112"));
+  }
+
+  @Test
+  public void shouldEnableRoute() throws Exception {
+    String service = "org.apache.dubbo.demo.DemoService";
+    String content = "conditions:\n"
+        + "- => host != 172.22.3.111\n"
+        + "- => host != 172.22.3.112\n"
+        + "enabled: false\n"
+        + "force: true\n"
+        + "key: " + service + "\n"
+        + "priority: 0\n"
+        + "runtime: false\n"
+        + "scope: service";
+    String path = "/dubbo/config/" + service + "/condition-router";
+    zkClient.create().creatingParentContainersIfNeeded().forPath(path);
+    zkClient.setData().forPath(path, content.getBytes());
+
+    byte[] bytes = zkClient.getData().forPath(path);
+    String updatedConfig = new String(bytes);
+    RoutingRule rule = YamlParser.loadObject(updatedConfig, RoutingRule.class);
+    assertFalse(rule.isEnabled());
+
+    restTemplate.put(url("/api/{env}/rules/route/condition/enable/{id}"), null, env, service);
+
+    bytes = zkClient.getData().forPath(path);
+    updatedConfig = new String(bytes);
+    rule = YamlParser.loadObject(updatedConfig, RoutingRule.class);
+    assertTrue(rule.isEnabled());
+  }
+
+  @Test
+  public void shouldDisableRoute() throws Exception {
+    String service = "org.apache.dubbo.demo.DemoService";
+    String content = "conditions:\n"
+        + "- => host != 172.22.3.111\n"
+        + "- => host != 172.22.3.112\n"
+        + "enabled: true\n"
+        + "force: false\n"
+        + "key: " + service + "\n"
+        + "priority: 0\n"
+        + "runtime: false\n"
+        + "scope: service";
+    String path = "/dubbo/config/" + service + "/condition-router";
+    zkClient.create().creatingParentContainersIfNeeded().forPath(path);
+    zkClient.setData().forPath(path, content.getBytes());
+
+    byte[] bytes = zkClient.getData().forPath(path);
+    String updatedConfig = new String(bytes);
+    RoutingRule rule = YamlParser.loadObject(updatedConfig, RoutingRule.class);
+    assertTrue(rule.isEnabled());
+
+    restTemplate.put(url("/api/{env}/rules/route/condition/disable/{id}"), null, env, service);
+
+    bytes = zkClient.getData().forPath(path);
+    updatedConfig = new String(bytes);
+    rule = YamlParser.loadObject(updatedConfig, RoutingRule.class);
+    assertFalse(rule.isEnabled());
+  }
+}
diff --git a/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/controller/ManagementControllerTest.java b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/controller/ManagementControllerTest.java
new file mode 100644
index 0000000..f4114ba
--- /dev/null
+++ b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/controller/ManagementControllerTest.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.admin.controller;
+
+import java.util.UUID;
+import org.apache.dubbo.admin.AbstractSpringIntegrationTest;
+import org.apache.dubbo.admin.common.util.Constants;
+import org.apache.dubbo.admin.model.dto.ConfigDTO;
+import org.apache.dubbo.admin.service.ProviderService;
+import org.junit.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.Matchers.hasSize;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class ManagementControllerTest extends AbstractSpringIntegrationTest {
+  private final String env = "whatever";
+
+  @MockBean
+  private ProviderService providerService;
+
+  @Test
+  public void shouldCreateGlobalConfig() throws Exception {
+    ConfigDTO configDTO = new ConfigDTO();
+    configDTO.setKey(Constants.GLOBAL_CONFIG);
+    configDTO.setConfig("key1=val1\nkey2=val2");
+    ResponseEntity<Boolean> responseEntity = restTemplate.postForEntity(
+        url("/api/{env}/manage/config"), configDTO, Boolean.class, env
+    );
+    assertEquals(responseEntity.getStatusCode(), HttpStatus.CREATED);
+    assertEquals(responseEntity.getBody(), true);
+
+    byte[] bytes = zkClient.getData().forPath(getPath("dubbo"));
+    String config = new String(bytes);
+    assertEquals(configDTO.getConfig(), config);
+
+    zkClient.delete().forPath(getPath("dubbo"));
+  }
+
+  @Test
+  public void shouldCreateApplicationConfig() throws Exception {
+    String uuid = UUID.randomUUID().toString();
+    String application = "dubbo-admin" + uuid;
+    ConfigDTO configDTO = new ConfigDTO();
+    configDTO.setKey(application);
+    configDTO.setConfig("key1=val1\nkey2=val2");
+    ResponseEntity<Boolean> responseEntity = restTemplate.postForEntity(
+        url("/api/{env}/manage/config"), configDTO, Boolean.class, env
+    );
+    assertEquals(responseEntity.getStatusCode(), HttpStatus.CREATED);
+    assertEquals(responseEntity.getBody(), true);
+
+    byte[] bytes = zkClient.getData().forPath(getPath(application));
+    String config = new String(bytes);
+    assertEquals(configDTO.getConfig(), config);
+  }
+
+  @Test
+  public void shouldThrowWhenUpdateNonExistedConfigKey() {
+    ConfigDTO configDTO = new ConfigDTO();
+    configDTO.setKey(Constants.GLOBAL_CONFIG);
+    configDTO.setConfig("key1=val1\nkey2=val2");
+    ResponseEntity<Void> responseEntity = restTemplate.exchange(
+        url("/api/{env}/manage/config/{key}"), HttpMethod.PUT,
+        new HttpEntity<>(configDTO), Void.class, env, "non-existed"
+    );
+    assertEquals(responseEntity.getStatusCode(), HttpStatus.NOT_FOUND);
+  }
+
+  @Test
+  public void shouldUpdateConfigSpecifiedKey() throws Exception {
+    String key = "shouldUpdateConfigSpecifiedKey";
+    ConfigDTO configDTO = new ConfigDTO();
+    configDTO.setKey(key);
+    configDTO.setConfig("key1=val1\nkey2=val2");
+    restTemplate.postForEntity(url("/api/{env}/manage/config"), configDTO, Boolean.class, env);
+
+    configDTO.setConfig("key1=updatedVal1\nkey2=updatedVal2");
+    ResponseEntity<Void> responseEntity = restTemplate.exchange(
+        url("/api/{env}/manage/config/{key}"), HttpMethod.PUT,
+        new HttpEntity<>(configDTO), Void.class, env, key
+    );
+    assertEquals(responseEntity.getStatusCode(), HttpStatus.OK);
+
+    byte[] bytes = zkClient.getData().forPath(getPath(key));
+    String config = new String(bytes);
+    assertEquals("key1=updatedVal1\nkey2=updatedVal2", config);
+  }
+
+  @Test
+  public void shouldGetAllConfig() throws Exception {
+    int num = 20;
+    List<ConfigDTO> configDTOs = new ArrayList<>(num);
+    for (int i = 0; i < num; i++) {
+      ConfigDTO configDTO = new ConfigDTO();
+      configDTO.setKey("key" + i);
+      configDTO.setConfig("key1=val1\nkey2=val2");
+      configDTOs.add(configDTO);
+
+      String path = getPath(configDTO.getKey());
+      if (zkClient.checkExists().forPath(path) == null) {
+        zkClient.create().creatingParentsIfNeeded().forPath(path);
+      }
+      zkClient.setData().forPath(path, configDTO.getConfig().getBytes());
+    }
+    when(providerService.findApplications())
+        .thenReturn(configDTOs.stream().map(ConfigDTO::getKey).collect(Collectors.toSet()));
+
+    ResponseEntity<List<ConfigDTO>> responseEntity = restTemplate.exchange(
+        url("/api/{env}/manage/config/{key}"), HttpMethod.GET,
+        null, new ParameterizedTypeReference<List<ConfigDTO>>() {
+        }, env, "*"
+    );
+    assertEquals(responseEntity.getStatusCode(), HttpStatus.OK);
+    assertThat(responseEntity.getBody(), hasSize(num));
+  }
+
+  @Test
+  public void shouldDeleteConfig() throws Exception {
+    int num = 20;
+    List<ConfigDTO> configDTOs = new ArrayList<>(num);
+    for (int i = 0; i < num; i++) {
+      ConfigDTO configDTO = new ConfigDTO();
+      configDTO.setKey("shouldDeleteConfigKey" + i);
+      configDTO.setConfig("key1=val1\nkey2=val2");
+      configDTOs.add(configDTO);
+
+      String path = getPath(configDTO.getKey());
+      if (zkClient.checkExists().forPath(path) == null) {
+        zkClient.create().creatingParentsIfNeeded().forPath(path);
+      }
+      zkClient.setData().forPath(path, configDTO.getConfig().getBytes());
+    }
+    when(providerService.findApplications())
+        .thenReturn(configDTOs.stream().map(ConfigDTO::getKey).collect(Collectors.toSet()));
+
+    restTemplate.delete(url("/api/{env}/manage/config/{key}"), env, "shouldDeleteConfigKey1");
+    ResponseEntity<List<ConfigDTO>> responseEntity = restTemplate.exchange(
+        url("/api/{env}/manage/config/{key}"), HttpMethod.GET,
+        null, new ParameterizedTypeReference<List<ConfigDTO>>() {
+        }, env, "*"
+    );
+    assertEquals(responseEntity.getStatusCode(), HttpStatus.OK);
+    assertThat(responseEntity.getBody(), hasSize(num - 1));
+
+    restTemplate.delete(url("/api/{env}/manage/config/{key}"), env, "shouldDeleteConfigKey10");
+    responseEntity = restTemplate.exchange(
+        url("/api/{env}/manage/config/{key}"), HttpMethod.GET,
+        null, new ParameterizedTypeReference<List<ConfigDTO>>() {
+        }, env, "*"
+    );
+    assertEquals(responseEntity.getStatusCode(), HttpStatus.OK);
+    assertThat(responseEntity.getBody(), hasSize(num - 2));
+  }
+
+  private String getPath(String key) {
+    return "/dubbo/" + Constants.CONFIG_KEY + Constants.PATH_SEPARATOR + key + Constants.PATH_SEPARATOR
+        + Constants.DUBBO_PROPERTY;
+  }
+}
diff --git a/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/controller/ServiceControllerTest.java b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/controller/ServiceControllerTest.java
index 7518675..1d9be1a 100644
--- a/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/controller/ServiceControllerTest.java
+++ b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/controller/ServiceControllerTest.java
@@ -25,7 +25,7 @@
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.support.AbstractRegistry;
-import org.junit.Before;
+import org.junit.After;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.ParameterizedTypeReference;
@@ -40,11 +40,14 @@
   @Autowired
   private Registry registry;
 
-  @Before
-  public void setUp() throws InterruptedException {
+  @After
+  public void tearDown() throws Exception {
     final Set<URL> registered = ((AbstractRegistry) registry).getRegistered();
     for (final URL url : registered) {
-      registry.unregister(url);
+      try {
+        registry.unregister(url);
+      } catch (Exception ignored) {
+      }
     }
     TimeUnit.SECONDS.sleep(1);
   }
@@ -92,9 +95,9 @@
   @Test
   public void shouldFilterUsingPattern() throws InterruptedException {
     final int num = 10;
+    final String application = "dubbo-admin";
     for (int i = 0; i < num; i++) {
       final String service = "org.apache.dubbo.admin.test.service" + i + ".pattern" + (i % 2);
-      final String application = "dubbo-admin";
       registry.register(generateProviderServiceUrl(application, service));
     }
     TimeUnit.SECONDS.sleep(1);
diff --git a/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/service/RegistryServerSyncTest.java b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/service/RegistryServerSyncTest.java
new file mode 100644
index 0000000..45e2747
--- /dev/null
+++ b/dubbo-admin-backend/src/test/java/org/apache/dubbo/admin/service/RegistryServerSyncTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.admin.service;
+
+import org.apache.dubbo.admin.common.util.Constants;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.registry.Registry;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+public class RegistryServerSyncTest {
+
+    @Mock
+    private Registry registry;
+
+    @InjectMocks
+    private RegistryServerSync registryServerSync;
+
+    @Test
+    public void testGetRegistryCache() {
+        registryServerSync.getRegistryCache();
+    }
+
+    @Test
+    public void testAfterPropertiesSet() throws Exception {
+        registryServerSync.afterPropertiesSet();
+        verify(registry).subscribe(any(URL.class), any(RegistryServerSync.class));
+    }
+
+    @Test
+    public void testDestroy() throws Exception {
+        registryServerSync.destroy();
+        verify(registry).unsubscribe(any(URL.class), any(RegistryServerSync.class));
+    }
+
+    @Test
+    public void testNotify() {
+        registryServerSync.notify(null);
+        registryServerSync.notify(Collections.emptyList());
+
+        // when url.getProtocol is not empty protocol
+        URL consumerUrl = mock(URL.class);
+        URL providerUrl = mock(URL.class);
+
+        when(consumerUrl.getParameter(Constants.CATEGORY_KEY, Constants.PROVIDERS_CATEGORY)).thenReturn(org.apache.dubbo.common.Constants.CONSUMER_PROTOCOL);
+        when(consumerUrl.getServiceInterface()).thenReturn("org.apache.dubbo.consumer");
+        when(consumerUrl.getServiceKey()).thenReturn("org.apache.dubbo.consumer");
+        when(consumerUrl.toFullString()).thenReturn("consumer://192.168.1.10/sunbufu.dubbo.consumer?application=dubbo&category=consumer&check=false&dubbo=2.7.0&interface=sunbufu.dubbo.consumer&loadbalabce=roundrobin&mehods=sayHi,sayGoodBye&owner=sunbufu&pid=18&protocol=dubbo&side=consumer&timeout=3000&timestamp=1548127407769");
+        when(providerUrl.getParameter(Constants.CATEGORY_KEY, Constants.PROVIDERS_CATEGORY)).thenReturn(org.apache.dubbo.common.Constants.PROVIDER_PROTOCOL);
+        when(providerUrl.getServiceInterface()).thenReturn("org.apache.dubbo.provider");
+        when(providerUrl.getServiceKey()).thenReturn("org.apache.dubbo.provider");
+        when(providerUrl.toFullString()).thenReturn("consumer://192.168.1.10/sunbufu.dubbo.consumer?application=dubbo&category=consumer&check=false&dubbo=2.6.2&interface=sunbufu.dubbo.consumer&loadbalabce=roundrobin&mehods=sayHi,sayGoodBye&owner=sunbufu&pid=18&protocol=dubbo&side=consumer&timeout=3000&timestamp=1548127407769");
+
+        registryServerSync.notify(Arrays.asList(consumerUrl, consumerUrl, providerUrl));
+
+        ConcurrentMap<String, Map<String, URL>> consumerMap = registryServerSync.getRegistryCache().get(org.apache.dubbo.common.Constants.CONSUMER_PROTOCOL);
+        assertTrue(consumerMap.keySet().contains("org.apache.dubbo.consumer"));
+        ConcurrentMap<String, Map<String, URL>> providerMap = registryServerSync.getRegistryCache().get(org.apache.dubbo.common.Constants.PROVIDER_PROTOCOL);
+        assertTrue(providerMap.keySet().contains("org.apache.dubbo.provider"));
+
+        // when url.getProtocol is empty protocol
+        when(consumerUrl.getProtocol()).thenReturn(org.apache.dubbo.common.Constants.EMPTY_PROTOCOL);
+        when(consumerUrl.getParameter(Constants.GROUP_KEY)).thenReturn("dubbo");
+        when(consumerUrl.getParameter(Constants.VERSION_KEY)).thenReturn("2.7.0");
+        registryServerSync.notify(Collections.singletonList(consumerUrl));
+
+        assertTrue(!consumerMap.keySet().contains("org.apache.dubbo.consumer"));
+
+        // when url's group or version is ANY_VALUE (*)
+        when(providerUrl.getProtocol()).thenReturn(org.apache.dubbo.common.Constants.EMPTY_PROTOCOL);
+        when(providerUrl.getParameter(Constants.GROUP_KEY)).thenReturn(Constants.ANY_VALUE);
+        registryServerSync.notify(Collections.singletonList(providerUrl));
+
+        assertTrue(!providerMap.keySet().contains("org.apache.dubbo.provider"));
+    }
+}
\ No newline at end of file
diff --git a/dubbo-admin-frontend/src/components/ServiceSearch.vue b/dubbo-admin-frontend/src/components/ServiceSearch.vue
index 1ddb566..2f69ae2 100644
--- a/dubbo-admin-frontend/src/components/ServiceSearch.vue
+++ b/dubbo-admin-frontend/src/components/ServiceSearch.vue
@@ -25,7 +25,7 @@
               <v-layout row wrap>
                 <v-combobox
                   id="serviceSearch"
-                  :loading="loading"
+                  :loading="searchLoading"
                   :items="typeAhead"
                   :search-input.sync="input"
                   v-model="filter"
@@ -147,11 +147,9 @@
         }
       ],
       timerID: null,
-      loading: false,
+      searchLoading: false,
       selected: 0,
-      serviceItem: [],
       input: null,
-      appItem: [],
       typeAhead: [],
       services: [],
       filter: '',
@@ -220,17 +218,13 @@
         // Simulated ajax query
         this.timerID = setTimeout(() => {
           if (v && v.length >= 4) {
-            this.loading = true
+            this.searchLoading = true
             if (this.selected === 0) {
-              this.typeAhead = this.serviceItem.filter(e => {
-                return (e || '').toLowerCase().indexOf((v || '').toLowerCase()) > -1
-              })
+              this.typeAhead = this.$store.getters.getServiceItems(v)
             } else if (this.selected === 2) {
-              this.typeAhead = this.appItem.filter(e => {
-                return (e || '').toLowerCase().indexOf((v || '').toLowerCase()) > -1
-              })
+              this.typeAhead = this.$store.getters.getAppItems(v)
             }
-            this.loading = false
+            this.searchLoading = false
             this.timerID = null
           } else {
             this.typeAhead = []
@@ -286,10 +280,11 @@
     },
     mounted: function () {
       this.setHeaders()
+      this.$store.dispatch('loadServiceItems')
+      this.$store.dispatch('loadAppItems')
       let query = this.$route.query
       let filter = null
       let pattern = null
-      let vm = this
       Object.keys(query).forEach(function (key) {
         if (key === 'filter') {
           filter = query[key]
@@ -315,19 +310,6 @@
         pattern = 'service'
         this.search(this.filter, pattern, true)
       }
-      this.$axios.get('/services')
-        .then(response => {
-          if (response.status === 200) {
-            vm.serviceItem = response.data
-          }
-        })
-
-      this.$axios.get('/applications')
-        .then(response => {
-          if (response.status === 200) {
-            vm.appItem = response.data
-          }
-        })
     }
 
   }
diff --git a/dubbo-admin-frontend/src/components/governance/AccessControl.vue b/dubbo-admin-frontend/src/components/governance/AccessControl.vue
index 0c59e0d..d4480ea 100644
--- a/dubbo-admin-frontend/src/components/governance/AccessControl.vue
+++ b/dubbo-admin-frontend/src/components/governance/AccessControl.vue
@@ -26,6 +26,10 @@
                 <v-combobox
                   id="serviceSearch"
                   v-model="filter"
+                  :loading="searchLoading"
+                  :items="typeAhead"
+                  :search-input.sync="input"
+                  @keyup.enter="search"
                   flat
                   append-icon=""
                   hide-no-data
@@ -240,6 +244,10 @@
     serviceHeaders: [],
     appHeaders: [],
     accesses: [],
+    searchLoading: false,
+    typeAhead: [],
+    input: null,
+    timerID: null,
     modal: {
       enable: false,
       readonly: false,
@@ -303,6 +311,26 @@
         }
       ]
     },
+    querySelections (v) {
+      if (this.timerID) {
+        clearTimeout(this.timerID)
+      }
+      // Simulated ajax query
+      this.timerID = setTimeout(() => {
+        if (v && v.length >= 4) {
+          this.searchLoading = true
+          if (this.selected === 0) {
+            this.typeAhead = this.$store.getters.getServiceItems(v)
+          } else if (this.selected === 1) {
+            this.typeAhead = this.$store.getters.getAppItems(v)
+          }
+          this.searchLoading = false
+          this.timerID = null
+        } else {
+          this.typeAhead = []
+        }
+      }, 500)
+    },
     search () {
       if (!this.filter) {
         this.filter = document.querySelector('#serviceSearch').value.trim()
@@ -468,6 +496,9 @@
     }
   },
   watch: {
+    input (val) {
+      this.querySelections(val)
+    },
     area () {
       this.setAppHeaders()
       this.setServiceHeaders()
@@ -476,6 +507,8 @@
   mounted () {
     this.setAppHeaders()
     this.setServiceHeaders()
+    this.$store.dispatch('loadServiceItems')
+    this.$store.dispatch('loadAppItems')
     let query = this.$route.query
     if ('service' in query) {
       this.filter = query['service']
diff --git a/dubbo-admin-frontend/src/components/governance/LoadBalance.vue b/dubbo-admin-frontend/src/components/governance/LoadBalance.vue
index 7db8fad..42771d9 100644
--- a/dubbo-admin-frontend/src/components/governance/LoadBalance.vue
+++ b/dubbo-admin-frontend/src/components/governance/LoadBalance.vue
@@ -25,6 +25,10 @@
               <v-layout row wrap>
                 <v-combobox
                   id="serviceSearch"
+                  :loading="searchLoading"
+                  :items="typeAhead"
+                  :search-input.sync="input"
+                  @keyup.enter="submit"
                   v-model="filter"
                   flat
                   append-icon=""
@@ -198,6 +202,10 @@
       warnText: '',
       warnStatus: {},
       height: 0,
+      searchLoading: false,
+      typeAhead: [],
+      input: null,
+      timerID: null,
       operations: [
         {id: 0, icon: 'visibility', tooltip: 'view'},
         {id: 1, icon: 'edit', tooltip: 'edit'},
@@ -274,6 +282,26 @@
           }
         ]
       },
+      querySelections (v) {
+        if (this.timerID) {
+          clearTimeout(this.timerID)
+        }
+        // Simulated ajax query
+        this.timerID = setTimeout(() => {
+          if (v && v.length >= 4) {
+            this.searchLoading = true
+            if (this.selected === 0) {
+              this.typeAhead = this.$store.getters.getServiceItems(v)
+            } else if (this.selected === 1) {
+              this.typeAhead = this.$store.getters.getAppItems(v)
+            }
+            this.searchLoading = false
+            this.timerID = null
+          } else {
+            this.typeAhead = []
+          }
+        }, 500)
+      },
       submit: function () {
         this.filter = document.querySelector('#serviceSearch').value.trim()
         this.search(this.filter, true)
@@ -448,6 +476,9 @@
       }
     },
     watch: {
+      input (val) {
+        this.querySelections(val)
+      },
       area () {
         this.setServiceHeaders()
         this.setAppHeaders()
@@ -456,6 +487,8 @@
     mounted: function () {
       this.setServiceHeaders()
       this.setAppHeaders()
+      this.$store.dispatch('loadServiceItems')
+      this.$store.dispatch('loadAppItems')
       this.ruleText = this.template
       let query = this.$route.query
       let filter = null
diff --git a/dubbo-admin-frontend/src/components/governance/Overrides.vue b/dubbo-admin-frontend/src/components/governance/Overrides.vue
index b2bedc6..d2f48b6 100644
--- a/dubbo-admin-frontend/src/components/governance/Overrides.vue
+++ b/dubbo-admin-frontend/src/components/governance/Overrides.vue
@@ -26,6 +26,10 @@
                 <v-combobox
                   id="serviceSearch"
                   v-model="filter"
+                  :loading="searchLoading"
+                  :items="typeAhead"
+                  :search-input.sync="input"
+                  @keyup.enter="submit"
                   flat
                   append-icon=""
                   hide-no-data
@@ -180,6 +184,10 @@
       warnStatus: {},
       height: 0,
       operations: operations,
+      searchLoading: false,
+      typeAhead: [],
+      input: null,
+      timerID: null,
       serviceConfigs: [
       ],
       appConfigs: [
@@ -229,6 +237,26 @@
           }
         ]
       },
+      querySelections (v) {
+        if (this.timerID) {
+          clearTimeout(this.timerID)
+        }
+        // Simulated ajax query
+        this.timerID = setTimeout(() => {
+          if (v && v.length >= 4) {
+            this.searchLoading = true
+            if (this.selected === 0) {
+              this.typeAhead = this.$store.getters.getServiceItems(v)
+            } else if (this.selected === 1) {
+              this.typeAhead = this.$store.getters.getAppItems(v)
+            }
+            this.searchLoading = false
+            this.timerID = null
+          } else {
+            this.typeAhead = []
+          }
+        }, 500)
+      },
       submit: function () {
         this.filter = document.querySelector('#serviceSearch').value.trim()
         this.search(this.filter, true)
@@ -435,6 +463,9 @@
       }
     },
     watch: {
+      input (val) {
+        this.querySelections(val)
+      },
       area () {
         this.setAppHeaders()
         this.setServiceHeaders()
@@ -443,6 +474,8 @@
     mounted: function () {
       this.setAppHeaders()
       this.setServiceHeaders()
+      this.$store.dispatch('loadServiceItems')
+      this.$store.dispatch('loadAppItems')
       this.ruleText = this.template
       let query = this.$route.query
       let filter = null
diff --git a/dubbo-admin-frontend/src/components/governance/RoutingRule.vue b/dubbo-admin-frontend/src/components/governance/RoutingRule.vue
index 5f95b4e..a033c4b 100644
--- a/dubbo-admin-frontend/src/components/governance/RoutingRule.vue
+++ b/dubbo-admin-frontend/src/components/governance/RoutingRule.vue
@@ -26,6 +26,10 @@
                 <v-combobox
                   id="serviceSearch"
                   v-model="filter"
+                  :loading="searchLoading"
+                  :items="typeAhead"
+                  :search-input.sync="input"
+                  @keyup.enter="submit"
                   flat
                   append-icon=""
                   hide-no-data
@@ -179,6 +183,10 @@
       warnText: '',
       warnStatus: {},
       height: 0,
+      searchLoading: false,
+      typeAhead: [],
+      input: null,
+      timerID: null,
       operations: operations,
       serviceRoutingRules: [
       ],
@@ -242,6 +250,26 @@
           }
         ]
       },
+      querySelections (v) {
+        if (this.timerID) {
+          clearTimeout(this.timerID)
+        }
+        // Simulated ajax query
+        this.timerID = setTimeout(() => {
+          if (v && v.length >= 4) {
+            this.searchLoading = true
+            if (this.selected === 0) {
+              this.typeAhead = this.$store.getters.getServiceItems(v)
+            } else if (this.selected === 1) {
+              this.typeAhead = this.$store.getters.getAppItems(v)
+            }
+            this.searchLoading = false
+            this.timerID = null
+          } else {
+            this.typeAhead = []
+          }
+        }, 500)
+      },
       submit: function () {
         this.filter = document.querySelector('#serviceSearch').value.trim()
         this.search(this.filter, true)
@@ -443,6 +471,9 @@
       }
     },
     watch: {
+      input (val) {
+        this.querySelections(val)
+      },
       area () {
         this.setAppHeaders()
         this.setServiceHeaders()
@@ -451,6 +482,8 @@
     mounted: function () {
       this.setAppHeaders()
       this.setServiceHeaders()
+      this.$store.dispatch('loadServiceItems')
+      this.$store.dispatch('loadAppItems')
       this.ruleText = this.template
       let query = this.$route.query
       let filter = null
diff --git a/dubbo-admin-frontend/src/components/governance/WeightAdjust.vue b/dubbo-admin-frontend/src/components/governance/WeightAdjust.vue
index 0bbcf5f..8f266c2 100644
--- a/dubbo-admin-frontend/src/components/governance/WeightAdjust.vue
+++ b/dubbo-admin-frontend/src/components/governance/WeightAdjust.vue
@@ -26,6 +26,10 @@
                 <v-combobox
                   id="serviceSearch"
                   v-model="filter"
+                  :items="typeAhead"
+                  :search-input.sync="input"
+                  :loading="searchLoading"
+                  @keyup.enter="submit"
                   flat
                   append-icon=""
                   hide-no-data
@@ -196,6 +200,10 @@
       warnText: '',
       warnStatus: {},
       height: 0,
+      searchLoading: false,
+      typeAhead: [],
+      input: null,
+      timerID: null,
       operations: [
         {id: 0, icon: 'visibility', tooltip: 'view'},
         {id: 1, icon: 'edit', tooltip: 'edit'},
@@ -239,6 +247,26 @@
           }
         ]
       },
+      querySelections (v) {
+        if (this.timerID) {
+          clearTimeout(this.timerID)
+        }
+        // Simulated ajax query
+        this.timerID = setTimeout(() => {
+          if (v && v.length >= 4) {
+            this.searchLoading = true
+            if (this.selected === 0) {
+              this.typeAhead = this.$store.getters.getServiceItems(v)
+            } else if (this.selected === 1) {
+              this.typeAhead = this.$store.getters.getAppItems(v)
+            }
+            this.searchLoading = false
+            this.timerID = null
+          } else {
+            this.typeAhead = []
+          }
+        }, 500)
+      },
       setAppHeaders: function () {
         this.appHeaders = [
           {
@@ -427,11 +455,16 @@
       area () {
         this.setAppHeaders()
         this.setServiceHeaders()
+      },
+      input (val) {
+        this.querySelections(val)
       }
     },
     mounted: function () {
       this.setAppHeaders()
       this.setServiceHeaders()
+      this.$store.dispatch('loadServiceItems')
+      this.$store.dispatch('loadAppItems')
       this.ruleText = this.template
       let query = this.$route.query
       let filter = null
diff --git a/dubbo-admin-frontend/src/store/index.js b/dubbo-admin-frontend/src/store/index.js
index a631f4d..f894271 100644
--- a/dubbo-admin-frontend/src/store/index.js
+++ b/dubbo-admin-frontend/src/store/index.js
@@ -23,17 +23,66 @@
 export const store = new Vuex.Store({
   state: {
     appTitle: 'Dubbo OPS',
-    area: null
+    area: null,
+    serviceItems: null,
+    appItems: null
   },
   mutations: {
     setArea (state, area) {
       state.area = area
+    },
+    setServiceItems (state, serviceItems) {
+      state.serviceItems = serviceItems
+    },
+    setAppItems (state, appItems) {
+      state.appItems = appItems
     }
   },
   actions: {
     changeArea ({commit}, area) {
       commit('setArea', area)
+    },
+    /**
+     * Load service items from server, put results into storage.
+     */
+    loadServiceItems ({commit}) {
+      Vue.prototype.$axios.get('/services')
+        .then(response => {
+          if (response.status === 200) {
+            const serviceItems = response.data
+            commit('setServiceItems', serviceItems)
+          }
+        })
+    },
+    /**
+     * Load application items from server, put results into storage.
+     */
+    loadAppItems ({commit}) {
+      Vue.prototype.$axios.get('/applications')
+        .then(response => {
+          if (response.status === 200) {
+            const appItems = response.data
+            commit('setAppItems', appItems)
+          }
+        })
     }
   },
-  getters: {}
+  getters: {
+    /**
+     * Get service item arrays with filter
+     */
+    getServiceItems: (state) => (filter) => {
+      return state.serviceItems.filter(e => {
+        return (e || '').toLowerCase().indexOf((filter || '').toLowerCase()) > -1
+      })
+    },
+    /**
+     * Get application item arrays with filter
+     */
+    getAppItems: (state) => (filter) => {
+      return state.appItems.filter(e => {
+        return (e || '').toLowerCase().indexOf((filter || '').toLowerCase()) > -1
+      })
+    }
+  }
 })
diff --git a/mvnw b/mvnw
new file mode 100755
index 0000000..5551fde
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,286 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   M2_HOME - location of maven2's installed home dir
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+  if [ -f /etc/mavenrc ] ; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ] ; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+  CYGWIN*) cygwin=true ;;
+  MINGW*) mingw=true;;
+  Darwin*) darwin=true
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+    if [ -z "$JAVA_HOME" ]; then
+      if [ -x "/usr/libexec/java_home" ]; then
+        export JAVA_HOME="`/usr/libexec/java_home`"
+      else
+        export JAVA_HOME="/Library/Java/Home"
+      fi
+    fi
+    ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=`java-config --jre-home`
+  fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+  ## resolve links - $0 may be a link to maven's home
+  PRG="$0"
+
+  # need this for relative symlinks
+  while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+      PRG="$link"
+    else
+      PRG="`dirname "$PRG"`/$link"
+    fi
+  done
+
+  saveddir=`pwd`
+
+  M2_HOME=`dirname "$PRG"`/..
+
+  # make it fully qualified
+  M2_HOME=`cd "$M2_HOME" && pwd`
+
+  cd "$saveddir"
+  # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --unix "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME="`(cd "$M2_HOME"; pwd)`"
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+  # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="`which javac`"
+  if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=`which readlink`
+    if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+      if $darwin ; then
+        javaHome="`dirname \"$javaExecutable\"`"
+        javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+      else
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
+      fi
+      javaHome="`dirname \"$javaExecutable\"`"
+      javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  if [ -n "$JAVA_HOME"  ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="`which java`"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+  if [ -z "$1" ]
+  then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ] ; do
+    if [ -d "$wdir"/.mvn ] ; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=`cd "$wdir/.."; pwd`
+    fi
+    # end of workaround
+  done
+  echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    echo "$(tr -s '\n' ' ' < "$1")"
+  fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+  exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Found .mvn/wrapper/maven-wrapper.jar"
+    fi
+else
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+    fi
+    jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+    while IFS="=" read key value; do
+      case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+      esac
+    done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Downloading from: $jarUrl"
+    fi
+    wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+
+    if command -v wget > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found wget ... using wget"
+        fi
+        wget "$jarUrl" -O "$wrapperJarPath"
+    elif command -v curl > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found curl ... using curl"
+        fi
+        curl -o "$wrapperJarPath" "$jarUrl"
+    else
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Falling back to using Java to download"
+        fi
+        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        if [ -e "$javaClass" ]; then
+            if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Compiling MavenWrapperDownloader.java ..."
+                fi
+                # Compiling the Java class
+                ("$JAVA_HOME/bin/javac" "$javaClass")
+            fi
+            if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                # Running the downloader
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Running MavenWrapperDownloader.java ..."
+                fi
+                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+            fi
+        fi
+    fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+  echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --path --windows "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100755
index 0000000..e5cfb0a
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,161 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
+	IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    echo Found %WRAPPER_JAR%
+) else (
+    echo Couldn't find %WRAPPER_JAR%, downloading it ...
+	echo Downloading from: %DOWNLOAD_URL%
+    powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
+    echo Finished downloading %WRAPPER_JAR%
+)
+@REM End of extension
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/pom.xml b/pom.xml
index 80a82b0..e1efff0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,6 +66,7 @@
 		<jedis-version>2.9.0</jedis-version>
         <apollo-version>1.2.0</apollo-version>
 		<snakeyaml-version>1.19</snakeyaml-version>
+		<maven-checkstyle-plugin-version>3.0.0</maven-checkstyle-plugin-version>
 	</properties>
 
 	<dependencyManagement>
@@ -146,6 +147,46 @@
 		</dependencies>
 	</dependencyManagement>
 
+	<profiles>
+		<profile>
+			<id>checkstyle</id>
+			<activation>
+				<jdk>[1.8,)</jdk>
+			</activation>
+			<build>
+				<plugins>
+					<plugin>
+						<groupId>org.apache.maven.plugins</groupId>
+						<artifactId>maven-checkstyle-plugin</artifactId>
+						<version>${maven-checkstyle-plugin-version}</version>
+						<dependencies>
+							<dependency>
+								<groupId>com.puppycrawl.tools</groupId>
+								<artifactId>checkstyle</artifactId>
+								<version>8.9</version>
+							</dependency>
+						</dependencies>
+						<executions>
+							<execution>
+								<id>checkstyle-validation</id>
+								<phase>validate</phase>
+								<configuration>
+									<configLocation>codestyle/checkstyle.xml</configLocation>
+									<encoding>UTF-8</encoding>
+									<consoleOutput>true</consoleOutput>
+									<failOnViolation>true</failOnViolation>
+								</configuration>
+								<goals>
+									<goal>check</goal>
+								</goals>
+							</execution>
+						</executions>
+					</plugin>
+				</plugins>
+			</build>
+		</profile>
+	</profiles>
+
 	<build>
 		<plugins>
 			<plugin>