Merge branch 'develop' into service-test
diff --git a/dubbo-admin-backend/pom.xml b/dubbo-admin-backend/pom.xml
index 0e29b39..8d04e90 100644
--- a/dubbo-admin-backend/pom.xml
+++ b/dubbo-admin-backend/pom.xml
@@ -87,6 +87,10 @@
             </exclusions>
         </dependency>
         <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-recipes</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>fastjson</artifactId>
         </dependency>
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/ServiceTestUtil.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/ServiceTestUtil.java
new file mode 100644
index 0000000..ad84fd0
--- /dev/null
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/util/ServiceTestUtil.java
@@ -0,0 +1,217 @@
+/*
+ * 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.apache.commons.lang3.StringUtils;
+import org.apache.dubbo.admin.model.domain.MethodMetadata;
+import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
+import org.apache.dubbo.metadata.definition.model.MethodDefinition;
+import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
+import org.apache.dubbo.metadata.definition.model.TypeDefinition;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class ServiceTestUtil {
+    private static Pattern COLLECTION_PATTERN = Pattern.compile("^java\\.util\\..*(Set|List|Queue|Collection|Deque)(<.*>)*$");
+    private static Pattern MAP_PATTERN = Pattern.compile("^java\\.util\\..*Map.*(<.*>)*$");
+
+    public static boolean sameMethod(MethodDefinition m, String methodSig) {
+        String name = m.getName();
+        String[] parameters = m.getParameterTypes();
+        StringBuilder sb = new StringBuilder();
+        sb.append(name).append("~");
+        for (String parameter : parameters) {
+            sb.append(parameter).append(";");
+        }
+        String sig = StringUtils.removeEnd(sb.toString(), ";");
+        return sig.equals(methodSig);
+    }
+
+    public static MethodMetadata generateMethodMeta(FullServiceDefinition serviceDefinition, MethodDefinition methodDefinition) {
+        MethodMetadata methodMetadata = new MethodMetadata();
+        String[] parameterTypes = methodDefinition.getParameterTypes();
+        String returnType = methodDefinition.getReturnType();
+        String signature = methodDefinition.getName() + "~" + Arrays.stream(parameterTypes).collect(Collectors.joining(";"));
+        methodMetadata.setSignature(signature);
+        methodMetadata.setReturnType(returnType);
+        List parameters = generateParameterTypes(parameterTypes, serviceDefinition);
+        methodMetadata.setParameterTypes(parameters);
+        return methodMetadata;
+    }
+
+    private static boolean isPrimitiveType(TypeDefinition td) {
+        String type = td.getType();
+        return type.equals("byte") || type.equals("java.lang.Byte") ||
+                type.equals("short") || type.equals("java.lang.Short") ||
+                type.equals("int") || type.equals("java.lang.Integer") ||
+                type.equals("long") || type.equals("java.lang.Long") ||
+                type.equals("float") || type.equals("java.lang.Float") ||
+                type.equals("double") || type.equals("java.lang.Double") ||
+                type.equals("boolean") || type.equals("java.lang.Boolean") ||
+                type.equals("void") || type.equals("java.lang.Void") ||
+                type.equals("java.lang.String") ||
+                type.equals("java.util.Date") ||
+                type.equals("java.lang.Object");
+    }
+
+    private static List generateParameterTypes(String[] parameterTypes, ServiceDefinition serviceDefinition) {
+        List parameters = new ArrayList();
+        for (String type : parameterTypes) {
+            Object result = generateType(serviceDefinition, type);
+            parameters.add(result);
+        }
+        return parameters;
+    }
+
+    private static TypeDefinition findTypeDefinition(ServiceDefinition serviceDefinition, String type) {
+        return serviceDefinition.getTypes().stream()
+                .filter(t -> t.getType().equals(type))
+                .findFirst().orElse(new TypeDefinition(type));
+    }
+
+    private static void generateComplexType(ServiceDefinition sd, TypeDefinition td, Map holder) {
+        for (Map.Entry<String, TypeDefinition> entry : td.getProperties().entrySet()) {
+            if (isPrimitiveType(td)) {
+                holder.put(entry.getKey(), generatePrimitiveType(td));
+            } else {
+                generateEnclosedType(holder, entry.getKey(), sd, entry.getValue());
+            }
+        }
+    }
+    private static Object generateComplexType(ServiceDefinition sd, TypeDefinition td) {
+        Map<String, Object> holder = new HashMap<>();
+        generateComplexType(sd, td, holder);
+        return holder;
+    }
+
+    private static boolean isMap(TypeDefinition td) {
+        String type = StringUtils.substringBefore(td.getType(), "<");
+        Matcher matcher = MAP_PATTERN.matcher(type);
+        return matcher.matches();
+    }
+
+    private static boolean isCollection(TypeDefinition td) {
+        String type = StringUtils.substringBefore(td.getType(), "<");
+        Matcher matcher = COLLECTION_PATTERN.matcher(type);
+        return matcher.matches();
+    }
+
+    private static boolean isArray(TypeDefinition td) {
+        return StringUtils.endsWith(td.getType(), "[]");
+    }
+
+    private static Object generatePrimitiveType(TypeDefinition td) {
+        String type = td.getType();
+        switch (type) {
+            case "byte":
+            case "java.lang.Byte":
+            case "short":
+            case "java.lang.Short":
+            case "int":
+            case "java.lang.Integer":
+            case "long":
+            case "java.lang.Long":
+                return 0;
+            case "float":
+            case "java.lang.Float":
+            case "double":
+            case "java.lang.Double":
+                return 0.0;
+            case "boolean":
+            case "java.lang.Boolean":
+                return true;
+            case "void":
+            case "java.lang.Void":
+                return null;
+            case "java.lang.String":
+                return "";
+            case "java.lang.Object":
+                return Collections.emptyMap();
+            case "java.util.Date":
+                return System.currentTimeMillis();
+            default:
+                return Collections.emptyMap();
+        }
+    }
+
+    private static Object generateType(ServiceDefinition sd, String type) {
+        TypeDefinition td = findTypeDefinition(sd, type);
+        return generateType(sd, td);
+    }
+
+    private static Object generateType(ServiceDefinition sd, TypeDefinition td) {
+        if (isPrimitiveType(td)) {
+            return generatePrimitiveType(td);
+        } else if (isMap(td)) {
+            return generateMapType(sd, td);
+        } else if (isArray(td)) {
+            return generateArrayType(sd, td);
+        } else if (isCollection(td)) {
+            return generateCollectionType(sd, td);
+        } else {
+            return generateComplexType(sd, td);
+        }
+    }
+
+    private static Object generateMapType(ServiceDefinition sd, TypeDefinition td) {
+        String keyType = StringUtils.substringAfter(td.getType(), "<");
+        keyType = StringUtils.substringBefore(keyType, ",");
+        keyType = StringUtils.strip(keyType);
+
+        Map<Object, Object> map = new HashMap<>();
+        // 生成 key 默认值
+        Object key = generateType(sd, keyType);
+
+        // 生成 value 默认值
+        String valueType = StringUtils.substringAfter(td.getType(), ",");
+        valueType = StringUtils.substringBefore(valueType, ">");
+        valueType = StringUtils.strip(valueType);
+        valueType = StringUtils.isNotEmpty(valueType) ? valueType : "java.lang.Object";
+        Object value = generateType(sd, valueType);
+        map.put(key, value);
+        return map;
+    }
+
+    private static Object generateCollectionType(ServiceDefinition sd, TypeDefinition td) {
+        String type = StringUtils.substringAfter(td.getType(), "<");
+        type = StringUtils.substringBefore(type, ">");
+        if (StringUtils.isEmpty(type)) {
+            // 如果 collection 类型未声明,则生成空 collection
+            return new Object[] {};
+        }
+        return new Object[]{generateType(sd, type)};
+
+    }
+    private static Object generateArrayType(ServiceDefinition sd, TypeDefinition td) {
+        String type = StringUtils.substringBeforeLast(td.getType(), "[]");
+        return new Object[]{generateType(sd, type)};
+    }
+
+    private static void generateEnclosedType(Map<String, Object> holder, String key, ServiceDefinition sd, TypeDefinition td) {
+        if (td.getProperties() == null || td.getProperties().size() == 0 || isPrimitiveType(td)) {
+            holder.put(key, generateType(sd, td));
+        } else {
+            Map<String, Object> enclosedMap = new HashMap<>();
+            holder.put(key, enclosedMap);
+            generateComplexType(sd, td, enclosedMap);
+        }
+    }
+}
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 0b48ec0..dfbc607 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
@@ -82,6 +82,10 @@
             FullServiceDefinition serviceDefinition = gson.fromJson(metadata, FullServiceDefinition.class);
             serviceDetailDTO.setMetadata(serviceDefinition);
         }
+        serviceDetailDTO.setConsumers(consumers);
+        serviceDetailDTO.setProviders(providers);
+        serviceDetailDTO.setService(service);
+        serviceDetailDTO.setApplication(application);
         return serviceDetailDTO;
     }
 
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ServiceTestController.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ServiceTestController.java
new file mode 100644
index 0000000..bf0497b
--- /dev/null
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/controller/ServiceTestController.java
@@ -0,0 +1,60 @@
+package org.apache.dubbo.admin.controller;
+
+import com.google.gson.Gson;
+import org.apache.dubbo.admin.common.util.ConvertUtil;
+import org.apache.dubbo.admin.common.util.ServiceTestUtil;
+import org.apache.dubbo.admin.model.domain.MethodMetadata;
+import org.apache.dubbo.admin.model.dto.ServiceTestDTO;
+import org.apache.dubbo.admin.service.ProviderService;
+import org.apache.dubbo.admin.service.impl.GenericServiceImpl;
+import org.apache.dubbo.common.Constants;
+import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
+import org.apache.dubbo.metadata.definition.model.MethodDefinition;
+import org.apache.dubbo.metadata.identifier.MetadataIdentifier;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/{env}/test")
+public class ServiceTestController {
+
+    @Autowired
+    private GenericServiceImpl genericService;
+
+    @Autowired
+    private ProviderService providerService;
+
+    @RequestMapping(method = RequestMethod.POST)
+    public Object test(@PathVariable String env, @RequestBody ServiceTestDTO serviceTestDTO) {
+        return genericService.invoke(serviceTestDTO.getService(), serviceTestDTO.getMethod(), serviceTestDTO.getParamaterTypes(), serviceTestDTO.getParams());
+//        return null;
+    }
+
+    @RequestMapping(value = "/method", method = RequestMethod.GET)
+    public MethodMetadata methodDetail(@PathVariable String env, @RequestParam String application, @RequestParam String service,
+                                       @RequestParam String method) {
+        Map<String, String> info = ConvertUtil.serviceName2Map(service);
+        MetadataIdentifier identifier = new MetadataIdentifier(info.get(Constants.INTERFACE_KEY),
+                info.get(Constants.VERSION_KEY),
+                info.get(Constants.GROUP_KEY), Constants.PROVIDER_SIDE, application);
+        String metadata = providerService.getProviderMetaData(identifier);
+        MethodMetadata methodMetadata = null;
+        if (metadata != null) {
+            Gson gson = new Gson();
+            FullServiceDefinition serviceDefinition = gson.fromJson(metadata, FullServiceDefinition.class);
+            List<MethodDefinition> methods = serviceDefinition.getMethods();
+            if (methods != null) {
+                for (MethodDefinition m : methods) {
+                    if (ServiceTestUtil.sameMethod(m, method)) {
+                        methodMetadata = ServiceTestUtil.generateMethodMeta(serviceDefinition, m);
+                        break;
+                    }
+                }
+            }
+        }
+        return methodMetadata;
+    }
+}
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/domain/MethodMetadata.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/domain/MethodMetadata.java
new file mode 100644
index 0000000..25a98c9
--- /dev/null
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/domain/MethodMetadata.java
@@ -0,0 +1,51 @@
+/*
+ * 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.model.domain;
+
+import java.util.List;
+import java.util.Map;
+
+public class MethodMetadata {
+    private String signature;
+    private List parameterTypes;
+    private String returnType;
+
+    public String getSignature() {
+        return signature;
+    }
+
+    public void setSignature(String signature) {
+        this.signature = signature;
+    }
+
+    public List getParameterTypes() {
+        return parameterTypes;
+    }
+
+    public void setParameterTypes(List parameterTypes) {
+        this.parameterTypes = parameterTypes;
+    }
+
+    public String getReturnType() {
+        return returnType;
+    }
+
+    public void setReturnType(String returnType) {
+        this.returnType = returnType;
+    }
+}
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/MethodDTO.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/MethodDTO.java
new file mode 100644
index 0000000..fdd6ffb
--- /dev/null
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/MethodDTO.java
@@ -0,0 +1,70 @@
+/*
+ * 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.model.dto;
+
+
+
+import java.util.List;
+
+public class MethodDTO {
+    private String application;
+    private String service;
+    private String name;
+    private List<String> parameterTypes;
+    private String returnType;
+
+    public String getApplication() {
+        return application;
+    }
+
+    public void setApplication(String application) {
+        this.application = application;
+    }
+
+    public String getService() {
+        return service;
+    }
+
+    public void setService(String service) {
+        this.service = service;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public List<String> getParameterTypes() {
+        return parameterTypes;
+    }
+
+    public void setParameterTypes(List<String> parameterTypes) {
+        this.parameterTypes = parameterTypes;
+    }
+
+    public String getReturnType() {
+        return returnType;
+    }
+
+    public void setReturnType(String returnType) {
+        this.returnType = returnType;
+    }
+}
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/ServiceDetailDTO.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/ServiceDetailDTO.java
index 2a738f5..1ef4beb 100644
--- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/ServiceDetailDTO.java
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/ServiceDetailDTO.java
@@ -27,9 +27,26 @@
 
     List<Provider> providers;
     List<Consumer> consumers;
+    private String service;
+    private String application;
 
     FullServiceDefinition metadata;
 
+    public String getService() {
+        return service;
+    }
+
+    public void setService(String service) {
+        this.service = service;
+    }
+
+    public String getApplication() {
+        return application;
+    }
+
+    public void setApplication(String application) {
+        this.application = application;
+    }
 
     public List<Provider> getProviders() {
         return providers;
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/ServiceTestDTO.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/ServiceTestDTO.java
new file mode 100644
index 0000000..7a398f7
--- /dev/null
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/dto/ServiceTestDTO.java
@@ -0,0 +1,40 @@
+package org.apache.dubbo.admin.model.dto;
+
+public class ServiceTestDTO {
+    private String service;
+    private String method;
+    private String[] paramaterTypes;
+    private Object[] params;
+
+    public String getService() {
+        return service;
+    }
+
+    public void setService(String service) {
+        this.service = service;
+    }
+
+    public String getMethod() {
+        return method;
+    }
+
+    public void setMethod(String method) {
+        this.method = method;
+    }
+
+    public String[] getParamaterTypes() {
+        return paramaterTypes;
+    }
+
+    public void setParamaterTypes(String[] paramaterTypes) {
+        this.paramaterTypes = paramaterTypes;
+    }
+
+    public Object[] getParams() {
+        return params;
+    }
+
+    public void setParams(Object[] params) {
+        this.params = params;
+    }
+}
diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/GenericServiceImpl.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/GenericServiceImpl.java
new file mode 100644
index 0000000..469ec9e
--- /dev/null
+++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/GenericServiceImpl.java
@@ -0,0 +1,50 @@
+package org.apache.dubbo.admin.service.impl;
+
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ReferenceConfig;
+import org.apache.dubbo.config.RegistryConfig;
+import org.apache.dubbo.registry.Registry;
+import org.apache.dubbo.rpc.service.GenericService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+@Component
+public class GenericServiceImpl {
+
+    private ReferenceConfig<GenericService> reference;
+
+    @Autowired
+    private Registry registry;
+
+    @PostConstruct
+    public void init() {
+        reference = new ReferenceConfig<>();
+        reference.setGeneric(true);
+
+        RegistryConfig registryConfig = new RegistryConfig();
+        registryConfig.setAddress(registry.getUrl().getProtocol() + "://" + registry.getUrl().getAddress());
+
+        ApplicationConfig applicationConfig = new ApplicationConfig();
+        applicationConfig.setName("dubbo-admin");
+        applicationConfig.setRegistry(registryConfig);
+
+        reference.setApplication(applicationConfig);
+    }
+
+    public Object invoke(String service, String method, String[] parameterTypes, Object[] params) {
+
+        reference.setInterface(service);
+        GenericService genericService = reference.get();
+        Object result = genericService.$invoke(method, parameterTypes, params);
+        System.out.println(result);
+        return result;
+    }
+
+    public static void main(String[] args) {
+        GenericServiceImpl genericService = new GenericServiceImpl();
+        genericService.init();
+        genericService.invoke("org.apache.dubbo.demo.api.DemoService", "sayHello", new String[]{"java.lang.String"}, new Object[]{"hello"});
+    }
+}
diff --git a/dubbo-admin-frontend/package.json b/dubbo-admin-frontend/package.json
index 3625632..6a0a9ce 100644
--- a/dubbo-admin-frontend/package.json
+++ b/dubbo-admin-frontend/package.json
@@ -15,6 +15,7 @@
     "brace": "^0.11.1",
     "http-status": "^1.2.0",
     "js-yaml": "^3.12.0",
+    "jsoneditor": "^5.26.2",
     "vue": "^2.5.2",
     "vue-i18n": "^8.6.0",
     "vue-router": "^3.0.1",
diff --git a/dubbo-admin-frontend/src/components/public/Footers.vue b/dubbo-admin-frontend/src/components/public/Footers.vue
index a5bbab6..331bea7 100644
--- a/dubbo-admin-frontend/src/components/public/Footers.vue
+++ b/dubbo-admin-frontend/src/components/public/Footers.vue
@@ -16,7 +16,7 @@
   -->
 
 <template>
-  <v-footer inset height="auto" class="pa-3 footer-border-top" app>
+  <v-footer inset height="auto" class="pa-3 footer-border-top">
     <v-spacer></v-spacer>
     <span class="caption mr-1"><strong>Copyright</strong> &copy;{{ new Date().getFullYear() }} <strong>The Apache Software Foundation.</strong></span>
   </v-footer>
diff --git a/dubbo-admin-frontend/src/components/public/JsonEditor.vue b/dubbo-admin-frontend/src/components/public/JsonEditor.vue
new file mode 100644
index 0000000..327e5ff
--- /dev/null
+++ b/dubbo-admin-frontend/src/components/public/JsonEditor.vue
@@ -0,0 +1,84 @@
+<!--
+  - 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.
+  -->
+<template>
+  <div class="jsoneditor-vue-container"></div>
+</template>
+
+<script>
+  import JSONEditor from 'jsoneditor'
+  import 'jsoneditor/dist/jsoneditor.css'
+
+  export default {
+    name: 'json-editor',
+    props: {
+      value: null,
+      mode: {
+        type: String,
+        default: 'tree'
+      },
+      modes: {
+        type: Array,
+        default: () => ['tree', 'code']
+      },
+      templates: Array
+    },
+    data () {
+      return {
+        $jsoneditor: null
+      }
+    },
+    watch: {
+      value (newVal, oldVal) {
+        if (newVal !== oldVal && this.$jsoneditor) {
+          this.$jsoneditor.update(newVal)
+        }
+      }
+    },
+    mounted () {
+      const options = {
+        name: 'Parameters',
+        navigationBar: false,
+        search: false,
+        mode: this.mode,
+        modes: this.modes,
+        onChange: () => {
+          if (this.$jsoneditor) {
+            var json = this.$jsoneditor.get()
+            this.$emit('input', json)
+          }
+        },
+        templates: this.templates
+      }
+      this.$jsoneditor = new JSONEditor(this.$el, options)
+      this.$jsoneditor.set(this.value)
+      this.$jsoneditor.expandAll()
+    },
+    beforeDestroy () {
+      if (this.$jsoneditor) {
+        this.$jsoneditor.destroy()
+        this.$jsoneditor = null
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .jsoneditor-vue-container {
+    width: 100%;
+    height: 100%;
+  }
+</style>
diff --git a/dubbo-admin-frontend/src/components/test/ServiceTest.vue b/dubbo-admin-frontend/src/components/test/ServiceTest.vue
index 971e2f4..a9568ad 100644
--- a/dubbo-admin-frontend/src/components/test/ServiceTest.vue
+++ b/dubbo-admin-frontend/src/components/test/ServiceTest.vue
@@ -14,32 +14,199 @@
   - See the License for the specific language governing permissions and
   - limitations under the License.
   -->
-
 <template>
-  <v-container
-    fill-height
-  >
-    <v-layout align-center>
-      <v-flex text-xs-center>
-        <h1 class="display-2 primary--text">{{$t('later.serviceTest')}}</h1>
-        <v-btn
-          href="#/service"
-          color="primary"
-          outline
-        >
-          {{$t('goIndex')}}
-        </v-btn>
+  <v-container grid-list-xl fluid>
+    <v-layout row wrap>
+      <v-flex xs12>
+        <search id="serviceSearch" v-model="filter" label="Search by service name" :submit="search"></search>
+      </v-flex>
+      <v-flex xs12>
+        <h3>Methods</h3>
+      </v-flex>
+      <v-flex xs12>
+        <v-data-table :headers="headers" :items="methods" hide-actions class="elevation-1">
+          <template slot="items" slot-scope="props">
+            <td>{{ props.item.name }}</td>
+            <td><v-chip xs v-for="(type, index) in props.item.parameterTypes" :key="index" label>{{ type }}</v-chip></td>
+            <td><v-chip label>{{ props.item.returnType }}</v-chip></td>
+            <td class="text-xs-right">
+              <v-tooltip bottom>
+                <v-btn
+                  fab dark small color="blue" slot="activator"
+                  :href="getHref(props.item.application, props.item.service, props.item.signature)"
+                >
+                  <v-icon>edit</v-icon>
+                </v-btn>
+                <span>Try it</span>
+              </v-tooltip>
+            </td>
+          </template>
+        </v-data-table>
       </v-flex>
     </v-layout>
+
+    <v-dialog v-model="modal.enable" width="1000px" persistent>
+      <v-card>
+        <v-card-title>
+          <span class="headline">Test {{ modal.method }}</span>
+        </v-card-title>
+        <v-container grid-list-xl fluid>
+          <v-layout row>
+            <v-flex lg6>
+              <json-editor v-model="modal.json" />
+            </v-flex>
+            <v-flex lg6>
+              <json-editor v-model="modal.json" />
+            </v-flex>
+          </v-layout>
+        </v-container>
+        <v-card-actions>
+          <v-spacer></v-spacer>
+          <v-btn color="darken-1"
+                 flat
+                 @click="modal.enable = false">Close</v-btn>
+          <v-btn color="primary"
+                 depressed
+                 @click="test">Execute</v-btn>
+        </v-card-actions>
+      </v-card>
+    </v-dialog>
   </v-container>
 </template>
 
 <script>
+  import JsonEditor from '@/components/public/JsonEditor'
+  import Search from '@/components/public/Search'
+
   export default {
-    name: 'ServiceTest'
+    name: 'ServiceTest',
+    data () {
+      return {
+        filter: '',
+        headers: [
+          {
+            text: 'Method Name',
+            value: 'method',
+            sortable: false
+          },
+          {
+            text: 'Parameter List',
+            value: 'parameter',
+            sortable: false
+          },
+          {
+            text: 'Return Type',
+            value: 'returnType',
+            sortable: false
+          },
+          {
+            text: '',
+            value: 'operation',
+            sortable: false
+          }
+        ],
+        service: null,
+        methods: [],
+        modal: {
+          method: null,
+          enable: false,
+          paramaterTypes: null,
+          json: []
+        }
+      }
+    },
+    methods: {
+      search () {
+        if (!this.filter) {
+          this.filter = document.querySelector('#serviceSearch').value.trim()
+          if (!this.filter) {
+            return
+          }
+        }
+        this.$router.push({
+          path: 'test',
+          query: { service: this.filter }
+        })
+        this.$axios.get('/service/' + this.filter).then(response => {
+          this.service = response.data
+          if (this.service.hasOwnProperty('metadata')) {
+            let data = this.service.metadata.methods
+            for (let i = 0; i < data.length; i++) {
+              let method = {}
+              let sig = data[i].name + '~'
+              let parameters = data[i].parameterTypes
+              let length = parameters.length
+              for (let j = 0; j < length; j++) {
+                sig = sig + parameters[j]
+                if (j !== length - 1) {
+                  sig = sig + ';'
+                }
+              }
+              method.signature = sig
+              method.name = data[i].name
+              method.parameterTypes = data[i].parameterTypes
+              method.returnType = data[i].returnType
+              method.service = response.data.service
+              method.application = response.data.application
+              this.methods.push(method)
+            }
+          }
+        }).catch(error => {
+          this.showSnackbar('error', error.response.data.message)
+        })
+      },
+      toTest (item) {
+        Object.assign(this.modal, {
+          enable: true,
+          method: item.name
+        })
+        this.modal.json = []
+        this.modal.paramaterTypes = item.parameterTypes
+        item.parameterTypes.forEach((i, index) => {
+          this.modal.json.push(this.getType(i))
+        })
+      },
+      getHref (application, service, method) {
+        let base = '/#/testMethod?'
+        let query = 'application=' + application + '&service=' + service + '&method=' + method
+        return base + query
+      },
+      test () {
+        this.$axios.post('/test', {
+          service: this.service.metadata.canonicalName,
+          method: this.modal.method,
+          paramaterTypes: this.modal.paramaterTypes,
+          params: this.modal.json
+        }).then(response => {
+          console.log(response)
+        })
+      },
+      getType (type) {
+        if (type.indexOf('java.util.List') === 0) {
+          return []
+        } else if (type.indexOf('java.util.Map') === 0) {
+          return []
+        } else {
+          return ''
+        }
+      }
+    },
+    mounted: function () {
+      let query = this.$route.query
+      let filter = null
+      Object.keys(query).forEach(function (key) {
+        if (key === 'service') {
+          filter = query[key]
+        }
+      })
+      if (filter !== null) {
+        this.filter = filter
+        this.search()
+      }
+    },
+    components: {
+      JsonEditor,
+      Search
+    }
   }
 </script>
-
-<style scoped>
-
-</style>
diff --git a/dubbo-admin-frontend/src/components/test/TestMethod.vue b/dubbo-admin-frontend/src/components/test/TestMethod.vue
new file mode 100644
index 0000000..9657f92
--- /dev/null
+++ b/dubbo-admin-frontend/src/components/test/TestMethod.vue
@@ -0,0 +1,129 @@
+<!--
+  - 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.
+  -->
+
+<template>
+  <v-container grid-list-xl fluid>
+    <v-layout row wrap>
+      <v-flex lg12>
+      </v-flex>
+      <v-flex lg12>
+        <v-card>
+          <v-flex lg8>
+            <v-card-title>
+              <span>Service: {{service}}<br> Method: {{method.name}}</span>
+            </v-card-title>
+            <v-card-text>
+              <json-editor id="test" v-model="method.json"/>
+            </v-card-text>
+            <v-card-actions>
+              <v-spacer></v-spacer>
+              <v-btn id="execute" mt-0 color="primary" @click="executeMethod()">EXECUTE</v-btn>
+            </v-card-actions>
+          </v-flex>
+          <v-flex lg8>
+            <v-card-text>
+              <h2>Test Result</h2>
+              <json-editor id="result" v-model="result" v-if="showResult"></json-editor>
+            </v-card-text>
+          </v-flex>
+        </v-card>
+      </v-flex>
+
+    </v-layout>
+
+  </v-container>
+</template>
+
+<script>
+  import JsonEditor from '@/components/public/JsonEditor'
+  export default {
+    name: 'TestMethod',
+    data () {
+      return {
+        basic: [],
+        service: null,
+        application: null,
+        method: {
+          name: null,
+          parameterTypes: [],
+          json: []
+        },
+        showResult: false,
+        result: {}
+      }
+    },
+
+    methods: {
+      executeMethod: function () {
+        let serviceTestDTO = {}
+        serviceTestDTO.service = this.service
+        serviceTestDTO.method = this.method.name
+        serviceTestDTO.parameterType = this.method.parameterTypes
+        serviceTestDTO.params = this.method.json
+        this.$axios.post('/test', serviceTestDTO)
+          .then(response => {
+            if (response.status === 200) {
+              this.result = response.data
+              this.showResult = true
+            }
+          })
+      }
+    },
+
+    mounted: function () {
+      let query = this.$route.query
+      let vm = this
+      let method = null
+      Object.keys(query).forEach(function (key) {
+        if (key === 'service') {
+          let item = {}
+          item.name = 'service'
+          item.value = query[key]
+          vm.basic.push(item)
+          vm.service = query[key]
+        }
+        if (key === 'method') {
+          let item = {}
+          item.name = 'method'
+          item.value = query[key]
+          vm.method.name = query[key].split('~')[0]
+          method = query[key]
+          vm.basic.push(item)
+        }
+        if (key === 'application') {
+          vm.application = query[key]
+        }
+      })
+      this.$axios.get('/test/method', {
+        params: {
+          application: vm.application,
+          service: vm.service,
+          method: method
+        }
+      }).then(response => {
+        this.method.json = response.data.parameterTypes
+      })
+    },
+    components: {
+      JsonEditor
+    }
+
+  }
+</script>
+
+<style scoped>
+</style>
diff --git a/dubbo-admin-frontend/src/router/index.js b/dubbo-admin-frontend/src/router/index.js
index 2ef4da4..35f8a90 100644
--- a/dubbo-admin-frontend/src/router/index.js
+++ b/dubbo-admin-frontend/src/router/index.js
@@ -19,6 +19,7 @@
 import Router from 'vue-router'
 import ServiceSearch from '@/components/ServiceSearch'
 import ServiceDetail from '@/components/ServiceDetail'
+import TestMethod from '@/components/test/TestMethod'
 import RoutingRule from '@/components/governance/RoutingRule'
 import TagRule from '@/components/governance/TagRule'
 import AccessControl from '@/components/governance/AccessControl'
@@ -45,6 +46,11 @@
       component: ServiceDetail
     },
     {
+      path: '/testMethod',
+      name: 'TestMethod',
+      component: TestMethod
+    },
+    {
       path: '/governance/routingRule',
       name: 'RoutingRule',
       component: RoutingRule
diff --git a/pom.xml b/pom.xml
index ad77093..c858050 100644
--- a/pom.xml
+++ b/pom.xml
@@ -111,6 +111,17 @@
 			</dependency>
 
 			<dependency>
+				<groupId>org.apache.curator</groupId>
+				<artifactId>curator-recipes</artifactId>
+				<version>${curator-version}</version>
+				<exclusions>
+					<exclusion>
+						<groupId>org.apache.zookeeper</groupId>
+						<artifactId>zookeeper</artifactId>
+					</exclusion>
+				</exclusions>
+			</dependency>
+			<dependency>
 				<groupId>com.alibaba</groupId>
 				<artifactId>fastjson</artifactId>
 				<version>${fastjson-version}</version>