You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by ea...@apache.org on 2022/06/29 06:19:55 UTC

[dubbo] branch 3.0 updated: [3.0-Triple] Support reflection api (#10168)

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

earthchen pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/3.0 by this push:
     new 47f7be2fd6 [3.0-Triple] Support reflection api (#10168)
47f7be2fd6 is described below

commit 47f7be2fd6137dc01433d9cd0e37204b0d350686
Author: GuoHao <gu...@gmail.com>
AuthorDate: Wed Jun 29 14:19:48 2022 +0800

    [3.0-Triple] Support reflection api (#10168)
    
    * Add proto
    
    * Update compiler
    
    * Add v1Alpha for compatibility
    
    * Abstract builtin service export
    
    * Try add get fd by name
    
    * get service by symbol now works
    
    * Add type
    
    * Support extension
    
    * Ignore reflection code scan
    
    * Remove v1
---
 .../org/apache/dubbo/gen/AbstractGenerator.java    |  66 ++++---
 .../src/main/resources/Dubbo3TripleStub.mustache   |   1 +
 .../org/apache/dubbo/rpc/stub/StubSuppliers.java   |   1 -
 .../tri/service/ReflectionV1AlphaService.java      | 202 +++++++++++++++++++++
 .../tri/service/SchemaDescriptorRegistry.java      |  90 +++++++++
 .../protocol/tri/service/TriBuiltinService.java    |  25 ++-
 .../src/main/proto/reflectionV1Alpha.proto         | 144 +++++++++++++++
 pom.xml                                            |   1 +
 8 files changed, 500 insertions(+), 30 deletions(-)

diff --git a/dubbo-compiler/src/main/java/org/apache/dubbo/gen/AbstractGenerator.java b/dubbo-compiler/src/main/java/org/apache/dubbo/gen/AbstractGenerator.java
index 587e9c310f..8d95ecdf87 100644
--- a/dubbo-compiler/src/main/java/org/apache/dubbo/gen/AbstractGenerator.java
+++ b/dubbo-compiler/src/main/java/org/apache/dubbo/gen/AbstractGenerator.java
@@ -72,22 +72,25 @@ public abstract class AbstractGenerator extends Generator {
     }
 
     @Override
-    public List<PluginProtos.CodeGeneratorResponse.File> generateFiles(PluginProtos.CodeGeneratorRequest request) throws GeneratorException {
+    public List<PluginProtos.CodeGeneratorResponse.File> generateFiles(
+        PluginProtos.CodeGeneratorRequest request) throws GeneratorException {
         final ProtoTypeMap typeMap = ProtoTypeMap.of(request.getProtoFileList());
 
         List<FileDescriptorProto> protosToGenerate = request.getProtoFileList().stream()
-                .filter(protoFile -> request.getFileToGenerateList().contains(protoFile.getName()))
-                .collect(Collectors.toList());
+            .filter(protoFile -> request.getFileToGenerateList().contains(protoFile.getName()))
+            .collect(Collectors.toList());
 
         List<ServiceContext> services = findServices(protosToGenerate, typeMap);
         return generateFiles(services);
     }
 
-    private List<ServiceContext> findServices(List<FileDescriptorProto> protos, ProtoTypeMap typeMap) {
+    private List<ServiceContext> findServices(List<FileDescriptorProto> protos,
+        ProtoTypeMap typeMap) {
         List<ServiceContext> contexts = new ArrayList<>();
 
         protos.forEach(fileProto -> {
-            for (int serviceNumber = 0; serviceNumber < fileProto.getServiceCount(); serviceNumber++) {
+            for (int serviceNumber = 0; serviceNumber < fileProto.getServiceCount();
+                serviceNumber++) {
                 ServiceContext serviceContext = buildServiceContext(
                     fileProto.getService(serviceNumber),
                     typeMap,
@@ -96,8 +99,12 @@ public abstract class AbstractGenerator extends Generator {
                 );
                 serviceContext.protoName = fileProto.getName();
                 serviceContext.packageName = extractPackageName(fileProto);
+                if (!Strings.isNullOrEmpty(fileProto.getOptions().getJavaOuterClassname())) {
+                    serviceContext.outerClassName = fileProto.getOptions().getJavaOuterClassname();
+                }
                 serviceContext.commonPackageName = extractCommonPackageName(fileProto);
-                serviceContext.multipleFiles = fileProto.getOptions() != null && fileProto.getOptions().getJavaMultipleFiles();
+                serviceContext.multipleFiles =
+                    fileProto.getOptions() != null && fileProto.getOptions().getJavaMultipleFiles();
                 contexts.add(serviceContext);
             }
         });
@@ -121,15 +128,18 @@ public abstract class AbstractGenerator extends Generator {
         return Strings.nullToEmpty(proto.getPackage());
     }
 
-    private ServiceContext buildServiceContext(ServiceDescriptorProto serviceProto, ProtoTypeMap typeMap, List<Location> locations, int serviceNumber) {
+    private ServiceContext buildServiceContext(ServiceDescriptorProto serviceProto,
+        ProtoTypeMap typeMap, List<Location> locations, int serviceNumber) {
         ServiceContext serviceContext = new ServiceContext();
-        serviceContext.fileName = getClassPrefix() + serviceProto.getName() + getClassSuffix() + ".java";
+        serviceContext.fileName =
+            getClassPrefix() + serviceProto.getName() + getClassSuffix() + ".java";
         serviceContext.className = getClassPrefix() + serviceProto.getName() + getClassSuffix();
-
+        serviceContext.outerClassName = serviceProto.getName() + "OuterClass";
         serviceContext.interfaceFileName = serviceProto.getName() + ".java";
         serviceContext.interfaceClassName = serviceProto.getName();
         serviceContext.serviceName = serviceProto.getName();
-        serviceContext.deprecated = serviceProto.getOptions() != null && serviceProto.getOptions().getDeprecated();
+        serviceContext.deprecated =
+            serviceProto.getOptions() != null && serviceProto.getOptions().getDeprecated();
 
         List<Location> allLocationsForService = locations.stream()
             .filter(location ->
@@ -143,7 +153,8 @@ public abstract class AbstractGenerator extends Generator {
             .filter(location -> location.getPathCount() == SERVICE_NUMBER_OF_PATHS)
             .findFirst()
             .orElseGet(Location::getDefaultInstance);
-        serviceContext.javaDoc = getJavaDoc(getComments(serviceLocation), getServiceJavaDocPrefix());
+        serviceContext.javaDoc = getJavaDoc(getComments(serviceLocation),
+            getServiceJavaDocPrefix());
 
         for (int methodNumber = 0; methodNumber < serviceProto.getMethodCount(); methodNumber++) {
             MethodContext methodContext = buildMethodContext(
@@ -160,13 +171,15 @@ public abstract class AbstractGenerator extends Generator {
         return serviceContext;
     }
 
-    private MethodContext buildMethodContext(MethodDescriptorProto methodProto, ProtoTypeMap typeMap, List<Location> locations, int methodNumber) {
+    private MethodContext buildMethodContext(MethodDescriptorProto methodProto,
+        ProtoTypeMap typeMap, List<Location> locations, int methodNumber) {
         MethodContext methodContext = new MethodContext();
         methodContext.originMethodName = methodProto.getName();
         methodContext.methodName = lowerCaseFirst(methodProto.getName());
         methodContext.inputType = typeMap.toJavaTypeName(methodProto.getInputType());
         methodContext.outputType = typeMap.toJavaTypeName(methodProto.getOutputType());
-        methodContext.deprecated = methodProto.getOptions() != null && methodProto.getOptions().getDeprecated();
+        methodContext.deprecated =
+            methodProto.getOptions() != null && methodProto.getOptions().getDeprecated();
         methodContext.isManyInput = methodProto.getClientStreaming();
         methodContext.isManyOutput = methodProto.getServerStreaming();
         methodContext.methodNumber = methodNumber;
@@ -203,7 +216,8 @@ public abstract class AbstractGenerator extends Generator {
         return Character.toLowerCase(s.charAt(0)) + s.substring(1);
     }
 
-    private List<PluginProtos.CodeGeneratorResponse.File> generateFiles(List<ServiceContext> services) {
+    private List<PluginProtos.CodeGeneratorResponse.File> generateFiles(
+        List<ServiceContext> services) {
         List<PluginProtos.CodeGeneratorResponse.File> allServiceFiles = new ArrayList<>();
         for (ServiceContext context : services) {
             List<PluginProtos.CodeGeneratorResponse.File> files = buildFile(context);
@@ -212,7 +226,7 @@ public abstract class AbstractGenerator extends Generator {
         return allServiceFiles;
     }
 
-    protected boolean enableMultipleTemplateFiles(){
+    protected boolean enableMultipleTemplateFiles() {
         return false;
     }
 
@@ -266,7 +280,8 @@ public abstract class AbstractGenerator extends Generator {
     }
 
     private String getComments(Location location) {
-        return location.getLeadingComments().isEmpty() ? location.getTrailingComments() : location.getLeadingComments();
+        return location.getLeadingComments().isEmpty() ? location.getTrailingComments()
+            : location.getLeadingComments();
     }
 
     private String getJavaDoc(String comments, String prefix) {
@@ -288,6 +303,7 @@ public abstract class AbstractGenerator extends Generator {
      * Template class for proto Service objects.
      */
     private class ServiceContext {
+
         // CHECKSTYLE DISABLE VisibilityModifier FOR 8 LINES
         public String fileName;
         public String interfaceFileName;
@@ -300,6 +316,8 @@ public abstract class AbstractGenerator extends Generator {
         public boolean deprecated;
         public String javaDoc;
         public boolean multipleFiles;
+
+        public String outerClassName;
         public List<MethodContext> methods = new ArrayList<>();
 
         public Set<String> methodTypes = new HashSet<>();
@@ -309,11 +327,13 @@ public abstract class AbstractGenerator extends Generator {
         }
 
         public List<MethodContext> unaryMethods() {
-            return methods.stream().filter(m -> (!m.isManyInput && !m.isManyOutput)).collect(Collectors.toList());
+            return methods.stream().filter(m -> (!m.isManyInput && !m.isManyOutput))
+                .collect(Collectors.toList());
         }
 
         public List<MethodContext> serverStreamingMethods() {
-            return methods.stream().filter(m -> !m.isManyInput && m.isManyOutput).collect(Collectors.toList());
+            return methods.stream().filter(m -> !m.isManyInput && m.isManyOutput)
+                .collect(Collectors.toList());
         }
 
         public List<MethodContext> biStreamingMethods() {
@@ -321,11 +341,13 @@ public abstract class AbstractGenerator extends Generator {
         }
 
         public List<MethodContext> biStreamingWithoutClientStreamMethods() {
-            return methods.stream().filter(m->m.isManyInput&&m.isManyOutput).collect(Collectors.toList());
+            return methods.stream().filter(m -> m.isManyInput && m.isManyOutput)
+                .collect(Collectors.toList());
         }
 
         public List<MethodContext> clientStreamingMethods() {
-            return methods.stream().filter(m->m.isManyInput&&!m.isManyOutput).collect(Collectors.toList());
+            return methods.stream().filter(m -> m.isManyInput && !m.isManyOutput)
+                .collect(Collectors.toList());
         }
 
         public List<MethodContext> methods() {
@@ -338,6 +360,7 @@ public abstract class AbstractGenerator extends Generator {
      * Template class for proto RPC objects.
      */
     private class MethodContext {
+
         // CHECKSTYLE DISABLE VisibilityModifier FOR 10 LINES
         public String originMethodName;
         public String methodName;
@@ -358,7 +381,8 @@ public abstract class AbstractGenerator extends Generator {
             for (int i = 0; i < methodName.length(); i++) {
                 char c = methodName.charAt(i);
                 s.append(Character.toUpperCase(c));
-                if ((i < methodName.length() - 1) && Character.isLowerCase(c) && Character.isUpperCase(methodName.charAt(i + 1))) {
+                if ((i < methodName.length() - 1) && Character.isLowerCase(c)
+                    && Character.isUpperCase(methodName.charAt(i + 1))) {
                     s.append('_');
                 }
             }
diff --git a/dubbo-compiler/src/main/resources/Dubbo3TripleStub.mustache b/dubbo-compiler/src/main/resources/Dubbo3TripleStub.mustache
index d6f6fa9fc2..77645108de 100644
--- a/dubbo-compiler/src/main/resources/Dubbo3TripleStub.mustache
+++ b/dubbo-compiler/src/main/resources/Dubbo3TripleStub.mustache
@@ -52,6 +52,7 @@ public final class {{className}} {
     private static final StubServiceDescriptor serviceDescriptor = new StubServiceDescriptor(SERVICE_NAME,{{interfaceClassName}}.class);
 
     static {
+        org.apache.dubbo.rpc.protocol.tri.service.SchemaDescriptorRegistry.addSchemaDescriptor(SERVICE_NAME,{{outerClassName}}.getDescriptor());
         StubSuppliers.addSupplier(SERVICE_NAME, {{className}}::newStub);
         StubSuppliers.addSupplier({{interfaceClassName}}.JAVA_SERVICE_NAME,  {{className}}::newStub);
         StubSuppliers.addDescriptor(SERVICE_NAME, serviceDescriptor);
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/StubSuppliers.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/StubSuppliers.java
index 6096caca0a..a56e52f643 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/StubSuppliers.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/StubSuppliers.java
@@ -33,7 +33,6 @@ public class StubSuppliers {
     public static void addDescriptor(String interfaceName, ServiceDescriptor serviceDescriptor) {
         SERVICE_DESCRIPTOR_MAP.put(interfaceName, serviceDescriptor);
     }
-
     public static void addSupplier(String interfaceName, Function<Invoker<?>, Object> supplier) {
         STUB_SUPPLIERS.put(interfaceName, supplier);
     }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/ReflectionV1AlphaService.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/ReflectionV1AlphaService.java
new file mode 100644
index 0000000000..b1a2659eb6
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/ReflectionV1AlphaService.java
@@ -0,0 +1,202 @@
+/*
+ * 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.rpc.protocol.tri.service;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.rpc.TriRpcStatus;
+
+import com.google.protobuf.Descriptors.FileDescriptor;
+import io.grpc.reflection.v1alpha.DubboServerReflectionTriple;
+import io.grpc.reflection.v1alpha.ErrorResponse;
+import io.grpc.reflection.v1alpha.ExtensionNumberResponse;
+import io.grpc.reflection.v1alpha.ExtensionRequest;
+import io.grpc.reflection.v1alpha.FileDescriptorResponse;
+import io.grpc.reflection.v1alpha.ListServiceResponse;
+import io.grpc.reflection.v1alpha.ServerReflectionRequest;
+import io.grpc.reflection.v1alpha.ServerReflectionResponse;
+import io.grpc.reflection.v1alpha.ServiceResponse;
+
+import java.util.ArrayDeque;
+import java.util.HashSet;
+import java.util.Queue;
+import java.util.Set;
+
+/**
+ * Provides a reflection service for Protobuf service for service test and dynamic gateway.
+ *
+ * @link https://github.com/grpc/grpc/blob/master/doc/server-reflection.md
+ */
+public class ReflectionV1AlphaService extends DubboServerReflectionTriple.ServerReflectionImplBase {
+
+
+    @Override
+    public StreamObserver<ServerReflectionRequest> serverReflectionInfo(
+        StreamObserver<ServerReflectionResponse> responseObserver) {
+        return new StreamObserver<ServerReflectionRequest>() {
+            @Override
+            public void onNext(ServerReflectionRequest request) {
+                switch (request.getMessageRequestCase()) {
+                    case FILE_BY_FILENAME:
+                        getFileByName(request, responseObserver);
+                        break;
+                    case FILE_CONTAINING_SYMBOL:
+                        getFileContainingSymbol(request, responseObserver);
+                        break;
+                    case FILE_CONTAINING_EXTENSION:
+                        getFileByExtension(request, responseObserver);
+                        break;
+                    case ALL_EXTENSION_NUMBERS_OF_TYPE:
+                        getAllExtensions(request, responseObserver);
+                        break;
+                    case LIST_SERVICES:
+                        listServices(request, responseObserver);
+                        break;
+                    default:
+                        sendErrorResponse(
+                            request,
+                            TriRpcStatus.Code.UNIMPLEMENTED,
+                            "not implemented " + request.getMessageRequestCase(), responseObserver);
+                }
+            }
+
+            @Override
+            public void onError(Throwable throwable) {
+                responseObserver.onError(throwable);
+            }
+
+            @Override
+            public void onCompleted() {
+                responseObserver.onCompleted();
+            }
+        };
+    }
+
+    private void getFileByName(ServerReflectionRequest request,
+        StreamObserver<ServerReflectionResponse> responseObserver) {
+        String name = request.getFileByFilename();
+        FileDescriptor fd = SchemaDescriptorRegistry.getSchemaDescriptor(name);
+        if (fd != null) {
+            responseObserver.onNext(createServerReflectionResponse(request, fd));
+        } else {
+            sendErrorResponse(request, TriRpcStatus.Code.NOT_FOUND, "File not found.",
+                responseObserver);
+        }
+    }
+
+    private void getFileContainingSymbol(ServerReflectionRequest request,
+        StreamObserver<ServerReflectionResponse> responseObserver) {
+        String symbol = request.getFileContainingSymbol();
+        FileDescriptor fd = SchemaDescriptorRegistry.getSchemaDescriptor(symbol);
+        if (fd != null) {
+            responseObserver.onNext(createServerReflectionResponse(request, fd));
+        } else {
+            sendErrorResponse(request, TriRpcStatus.Code.NOT_FOUND, "Symbol not found.",
+                responseObserver);
+        }
+    }
+
+    private void getFileByExtension(ServerReflectionRequest request,
+        StreamObserver<ServerReflectionResponse> responseObserver) {
+        ExtensionRequest extensionRequest = request.getFileContainingExtension();
+        String type = extensionRequest.getContainingType();
+        int extension = extensionRequest.getExtensionNumber();
+        FileDescriptor fd =
+            SchemaDescriptorRegistry.getFileDescriptorByExtensionAndNumber(type, extension);
+        if (fd != null) {
+            responseObserver.onNext(createServerReflectionResponse(request, fd));
+        } else {
+            sendErrorResponse(request, TriRpcStatus.Code.NOT_FOUND, "Extension not found.",
+                responseObserver);
+        }
+    }
+
+    private void getAllExtensions(ServerReflectionRequest request,
+        StreamObserver<ServerReflectionResponse> responseObserver) {
+        String type = request.getAllExtensionNumbersOfType();
+        Set<Integer> extensions = SchemaDescriptorRegistry.getExtensionNumbers(type);
+        if (extensions != null) {
+            ExtensionNumberResponse.Builder builder =
+                ExtensionNumberResponse.newBuilder()
+                    .setBaseTypeName(type)
+                    .addAllExtensionNumber(extensions);
+            responseObserver.onNext(
+                ServerReflectionResponse.newBuilder()
+                    .setValidHost(request.getHost())
+                    .setOriginalRequest(request)
+                    .setAllExtensionNumbersResponse(builder)
+                    .build());
+        } else {
+            sendErrorResponse(request, TriRpcStatus.Code.NOT_FOUND, "Type not found.",
+                responseObserver);
+        }
+    }
+
+    private void listServices(ServerReflectionRequest request,
+        StreamObserver<ServerReflectionResponse> responseObserver) {
+        ListServiceResponse.Builder builder = ListServiceResponse.newBuilder();
+
+        for (String serviceName : SchemaDescriptorRegistry.listServiceNames()) {
+            builder.addService(ServiceResponse.newBuilder().setName(serviceName));
+        }
+        responseObserver.onNext(
+            ServerReflectionResponse.newBuilder()
+                .setValidHost(request.getHost())
+                .setOriginalRequest(request)
+                .setListServicesResponse(builder)
+                .build());
+    }
+
+    private void sendErrorResponse(
+        ServerReflectionRequest request, TriRpcStatus.Code code, String message,
+        StreamObserver<ServerReflectionResponse> responseObserver) {
+        ServerReflectionResponse response =
+            ServerReflectionResponse.newBuilder()
+                .setValidHost(request.getHost())
+                .setOriginalRequest(request)
+                .setErrorResponse(
+                    ErrorResponse.newBuilder()
+                        .setErrorCode(code.code)
+                        .setErrorMessage(message))
+                .build();
+        responseObserver.onNext(response);
+    }
+
+    private ServerReflectionResponse createServerReflectionResponse(
+        ServerReflectionRequest request, FileDescriptor fd) {
+        FileDescriptorResponse.Builder fdRBuilder = FileDescriptorResponse.newBuilder();
+        Set<String> seenFiles = new HashSet<>();
+        Queue<FileDescriptor> frontier = new ArrayDeque<>();
+        seenFiles.add(fd.getName());
+        frontier.add(fd);
+        while (!frontier.isEmpty()) {
+            FileDescriptor nextFd = frontier.remove();
+            fdRBuilder.addFileDescriptorProto(nextFd.toProto().toByteString());
+            for (FileDescriptor dependencyFd : nextFd.getDependencies()) {
+                if (!seenFiles.contains(dependencyFd.getName())) {
+                    seenFiles.add(dependencyFd.getName());
+                    frontier.add(dependencyFd);
+                }
+            }
+        }
+        return ServerReflectionResponse.newBuilder()
+            .setValidHost(request.getHost())
+            .setOriginalRequest(request)
+            .setFileDescriptorResponse(fdRBuilder)
+            .build();
+    }
+}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/SchemaDescriptorRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/SchemaDescriptorRegistry.java
new file mode 100644
index 0000000000..3d37505ae1
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/SchemaDescriptorRegistry.java
@@ -0,0 +1,90 @@
+/*
+ * 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.rpc.protocol.tri.service;
+
+import com.google.protobuf.Descriptors.Descriptor;
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.Descriptors.FileDescriptor;
+
+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.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class SchemaDescriptorRegistry {
+
+    private static final Map<String, FileDescriptor> DESCRIPTORS_BY_SYMBOL = new ConcurrentHashMap<>();
+
+    private static final Map<String, Map<Integer, FileDescriptor>> EXTENSIONS = new ConcurrentHashMap<>();
+
+    private static final Set<String> SERVICES = new HashSet<>();
+
+    public static void addSchemaDescriptor(String serviceName, FileDescriptor fd) {
+        SERVICES.add(serviceName);
+        DESCRIPTORS_BY_SYMBOL.put(serviceName, fd);
+        for (Descriptor messageType : fd.getMessageTypes()) {
+            addType(messageType);
+        }
+        for (FieldDescriptor extension : fd.getExtensions()) {
+            addExtension(extension, fd);
+        }
+    }
+
+    private static void addType(Descriptor descriptor) {
+        DESCRIPTORS_BY_SYMBOL.put(descriptor.getFullName(), descriptor.getFile());
+        for (Descriptor nestedType : descriptor.getNestedTypes()) {
+            addType(nestedType);
+        }
+    }
+
+    private static void addExtension(FieldDescriptor extension, FileDescriptor fd) {
+        String name = extension.getContainingType().getFullName();
+        int number = extension.getNumber();
+        if (!EXTENSIONS.containsKey(name)) {
+            EXTENSIONS.put(name, new HashMap<>());
+        }
+        Map<Integer, FileDescriptor> fdMap = EXTENSIONS.get(name);
+        fdMap.put(number, fd);
+    }
+
+    public static FileDescriptor getFileDescriptorByExtensionAndNumber(String extension,
+        int number) {
+        return EXTENSIONS.getOrDefault(extension, Collections.emptyMap()).get(number);
+    }
+
+    public static Set<Integer> getExtensionNumbers(String extension) {
+        Map<Integer, FileDescriptor> ret = EXTENSIONS.get(extension);
+        if (ret == null) {
+            return null;
+        } else {
+            return ret.keySet();
+        }
+    }
+
+    public static FileDescriptor getSchemaDescriptor(String serviceName) {
+        return DESCRIPTORS_BY_SYMBOL.get(serviceName);
+    }
+
+    public static List<String> listServiceNames() {
+        return new ArrayList<>(SERVICES);
+    }
+}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/TriBuiltinService.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/TriBuiltinService.java
index 35d6ad48ea..2bf3ca0ece 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/TriBuiltinService.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/TriBuiltinService.java
@@ -24,6 +24,7 @@ import org.apache.dubbo.rpc.PathResolver;
 import org.apache.dubbo.rpc.ProxyFactory;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.model.ModuleModel;
 
 import io.grpc.health.v1.DubboHealthTriple;
 import io.grpc.health.v1.Health;
@@ -44,6 +45,7 @@ public class TriBuiltinService {
 
     private final Health healthService;
 
+    private final ReflectionV1AlphaService reflectionServiceV1Alpha;
     private final HealthStatusManager healthStatusManager;
 
     private final AtomicBoolean init = new AtomicBoolean();
@@ -51,6 +53,7 @@ public class TriBuiltinService {
     public TriBuiltinService(FrameworkModel frameworkModel) {
         healthStatusManager = new HealthStatusManager(new TriHealthImpl());
         healthService = healthStatusManager.getHealthService();
+        reflectionServiceV1Alpha = new ReflectionV1AlphaService();
         proxyFactory = frameworkModel.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
         pathResolver = frameworkModel.getExtensionLoader(PathResolver.class).getDefaultExtension();
         init();
@@ -58,17 +61,23 @@ public class TriBuiltinService {
 
     public void init() {
         if (init.compareAndSet(false, true)) {
-            URL url = new ServiceConfigURL(CommonConstants.TRIPLE, null, null, ANYHOST_VALUE, 0,
-                DubboHealthTriple.SERVICE_NAME)
-                .addParameter(PROXY_KEY, CommonConstants.NATIVE_STUB)
-                .setScopeModel(ApplicationModel.defaultModel().getInternalModule());
-            Invoker<?> invoker = proxyFactory.getInvoker(healthService, Health.class, url);
-            pathResolver.add(DubboHealthTriple.SERVICE_NAME, invoker);
-            ApplicationModel.defaultModel().getInternalModule()
-                .addDestroyListener(scopeModel -> pathResolver.remove(DubboHealthTriple.SERVICE_NAME));
+            addSingleBuiltinService(DubboHealthTriple.SERVICE_NAME, healthService, Health.class);
+            addSingleBuiltinService(ReflectionV1AlphaService.SERVICE_NAME, reflectionServiceV1Alpha,
+                ReflectionV1AlphaService.class);
         }
     }
 
+    private <T> void addSingleBuiltinService(String serviceName, T impl, Class<T> interfaceClass) {
+        ModuleModel internalModule = ApplicationModel.defaultModel().getInternalModule();
+        URL url = new ServiceConfigURL(CommonConstants.TRIPLE, null, null, ANYHOST_VALUE, 0,
+            serviceName)
+            .addParameter(PROXY_KEY, CommonConstants.NATIVE_STUB)
+            .setScopeModel(internalModule);
+        Invoker<?> invoker = proxyFactory.getInvoker(impl, interfaceClass, url);
+        pathResolver.add(serviceName, invoker);
+        internalModule.addDestroyListener(scopeModel -> pathResolver.remove(serviceName));
+    }
+
     public HealthStatusManager getHealthStatusManager() {
         return healthStatusManager;
     }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/proto/reflectionV1Alpha.proto b/dubbo-rpc/dubbo-rpc-triple/src/main/proto/reflectionV1Alpha.proto
new file mode 100644
index 0000000000..8c5e06fe14
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/proto/reflectionV1Alpha.proto
@@ -0,0 +1,144 @@
+// Copyright 2016 The gRPC Authors
+//
+// Licensed 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.
+// Service exported by server reflection
+
+
+// Warning: this entire file is deprecated. Use this instead:
+// https://github.com/grpc/grpc-proto/blob/master/grpc/reflection/v1/reflection.proto
+
+syntax = "proto3";
+
+package grpc.reflection.v1alpha;
+
+option deprecated = true;
+option java_multiple_files = true;
+option java_package = "io.grpc.reflection.v1alpha";
+option java_outer_classname = "ServerReflectionProto";
+
+service ServerReflection {
+  // The reflection service is structured as a bidirectional stream, ensuring
+  // all related requests go to a single server.
+  rpc ServerReflectionInfo(stream ServerReflectionRequest)
+      returns (stream ServerReflectionResponse);
+}
+
+// The message sent by the client when calling ServerReflectionInfo method.
+message ServerReflectionRequest {
+  string host = 1;
+  // To use reflection service, the client should set one of the following
+  // fields in message_request. The server distinguishes requests by their
+  // defined field and then handles them using corresponding methods.
+  oneof message_request {
+    // Find a proto file by the file name.
+    string file_by_filename = 3;
+
+    // Find the proto file that declares the given fully-qualified symbol name.
+    // This field should be a fully-qualified symbol name
+    // (e.g. <package>.<service>[.<method>] or <package>.<type>).
+    string file_containing_symbol = 4;
+
+    // Find the proto file which defines an extension extending the given
+    // message type with the given field number.
+    ExtensionRequest file_containing_extension = 5;
+
+    // Finds the tag numbers used by all known extensions of extendee_type, and
+    // appends them to ExtensionNumberResponse in an undefined order.
+    // Its corresponding method is best-effort: it's not guaranteed that the
+    // reflection service will implement this method, and it's not guaranteed
+    // that this method will provide all extensions. Returns
+    // StatusCode::UNIMPLEMENTED if it's not implemented.
+    // This field should be a fully-qualified type name. The format is
+    // <package>.<type>
+    string all_extension_numbers_of_type = 6;
+
+    // List the full names of registered services. The content will not be
+    // checked.
+    string list_services = 7;
+  }
+}
+
+// The type name and extension number sent by the client when requesting
+// file_containing_extension.
+message ExtensionRequest {
+  // Fully-qualified type name. The format should be <package>.<type>
+  string containing_type = 1;
+  int32 extension_number = 2;
+}
+
+// The message sent by the server to answer ServerReflectionInfo method.
+message ServerReflectionResponse {
+  string valid_host = 1;
+  ServerReflectionRequest original_request = 2;
+  // The server set one of the following fields accroding to the message_request
+  // in the request.
+  oneof message_response {
+    // This message is used to answer file_by_filename, file_containing_symbol,
+    // file_containing_extension requests with transitive dependencies. As
+    // the repeated label is not allowed in oneof fields, we use a
+    // FileDescriptorResponse message to encapsulate the repeated fields.
+    // The reflection service is allowed to avoid sending FileDescriptorProtos
+    // that were previously sent in response to earlier requests in the stream.
+    FileDescriptorResponse file_descriptor_response = 4;
+
+    // This message is used to answer all_extension_numbers_of_type requst.
+    ExtensionNumberResponse all_extension_numbers_response = 5;
+
+    // This message is used to answer list_services request.
+    ListServiceResponse list_services_response = 6;
+
+    // This message is used when an error occurs.
+    ErrorResponse error_response = 7;
+  }
+}
+
+// Serialized FileDescriptorProto messages sent by the server answering
+// a file_by_filename, file_containing_symbol, or file_containing_extension
+// request.
+message FileDescriptorResponse {
+  // Serialized FileDescriptorProto messages. We avoid taking a dependency on
+  // descriptor.proto, which uses proto2 only features, by making them opaque
+  // bytes instead.
+  repeated bytes file_descriptor_proto = 1;
+}
+
+// A list of extension numbers sent by the server answering
+// all_extension_numbers_of_type request.
+message ExtensionNumberResponse {
+  // Full name of the base type, including the package name. The format
+  // is <package>.<type>
+  string base_type_name = 1;
+  repeated int32 extension_number = 2;
+}
+
+// A list of ServiceResponse sent by the server answering list_services request.
+message ListServiceResponse {
+  // The information of each service may be expanded in the future, so we use
+  // ServiceResponse message to encapsulate it.
+  repeated ServiceResponse service = 1;
+}
+
+// The information of a single service used by ListServiceResponse to answer
+// list_services request.
+message ServiceResponse {
+  // Full name of a registered service, including its package name. The format
+  // is <package>.<service>
+  string name = 1;
+}
+
+// The error code and error message sent by the server when an error occurs.
+message ErrorResponse {
+  // This field uses the error codes defined in grpc::StatusCode.
+  int32 error_code = 1;
+  string error_message = 2;
+}
diff --git a/pom.xml b/pom.xml
index afb9b5c1c5..54b105f0d9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -331,6 +331,7 @@
                                         **/com/google/rpc/*,
                                         **/generated/**/*,
                                         **/grpc/health/**/*,
+                                        **/grpc/reflection/**/*,
                                         **/target/**/*,
                                         **/*.json
                                     </excludes>