This is an automated email from the ASF dual-hosted git repository.
zrlw pushed a commit to branch 3.3
in repository https://gitbox.apache.org/repos/asf/dubbo.git
The following commit(s) were added to refs/heads/3.3 by this push:
new ed0e11e1cc Fix Triple method resolution for overloaded wrapper methods
(#16325)
ed0e11e1cc is described below
commit ed0e11e1cca847b632b32a3e5f61ff8f072ada84
Author: shinyruo <[email protected]>
AuthorDate: Mon Jun 29 16:17:32 2026 +0800
Fix Triple method resolution for overloaded wrapper methods (#16325)
* Fix Triple method resolution for overloaded wrapper methods
Fixes #14939
Signed-off-by: bubu <[email protected]>
* Address response type comparison review
* Handle ambiguous Triple reflection resolution
---------
Signed-off-by: bubu <[email protected]>
Co-authored-by: zrlw <[email protected]>
---
.../dubbo/rpc/protocol/tri/DescriptorUtils.java | 177 ++++++++++++++++-----
.../rpc/protocol/tri/DescriptorUtilsTest.java | 155 ++++++++++++++++++
2 files changed, 289 insertions(+), 43 deletions(-)
diff --git
a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtils.java
b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtils.java
index e8f34431e2..f04b3011d2 100644
---
a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtils.java
+++
b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtils.java
@@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.protocol.tri;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.stream.StreamObserver;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.remoting.http12.exception.UnimplementedException;
import org.apache.dubbo.rpc.Invoker;
@@ -30,6 +31,7 @@ import org.apache.dubbo.rpc.stub.StubSuppliers;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
@@ -86,62 +88,151 @@ public final class DescriptorUtils {
public static MethodDescriptor findReflectionMethodDescriptor(
ServiceDescriptor serviceDescriptor, String methodName) {
- MethodDescriptor methodDescriptor = null;
+ MethodDescriptor methodDescriptor =
findWellKnownMethodDescriptor(methodName);
+ if (methodDescriptor != null) {
+ return methodDescriptor;
+ }
+
+ List<MethodDescriptor> methodDescriptors =
serviceDescriptor.getMethods(methodName);
+ methodDescriptor =
findSingleOrGeneratedUnaryMethodDescriptor(methodDescriptors);
+ if (methodDescriptor == null &&
CollectionUtils.isNotEmpty(methodDescriptors)) {
+ throw new UnimplementedException("method:" + methodName);
+ }
+ return methodDescriptor;
+ }
+
+ public static MethodDescriptor findTripleMethodDescriptor(
+ ServiceDescriptor serviceDescriptor, String methodName,
InputStream rawMessage) throws IOException {
+ MethodDescriptor methodDescriptor =
findWellKnownMethodDescriptor(methodName);
+ if (methodDescriptor != null) {
+ return methodDescriptor;
+ }
+
+ List<MethodDescriptor> methodDescriptors =
serviceDescriptor.getMethods(methodName);
+ if (CollectionUtils.isEmpty(methodDescriptors)) {
+ throw new UnimplementedException("method:" + methodName);
+ }
+ if (methodDescriptors.size() == 1) {
+ return methodDescriptors.get(0);
+ }
+
+ TripleRequestWrapper request = parseRequestWrapper(rawMessage);
+ List<String> argTypes = request == null ? null : request.getArgTypes();
+ if (argTypes != null) {
+ MethodDescriptor matchedDescriptor =
+ findMethodDescriptorByParamTypes(methodDescriptors,
argTypes.toArray(new String[0]));
+ if (matchedDescriptor != null) {
+ return matchedDescriptor;
+ }
+ if (CollectionUtils.isNotEmpty(argTypes)) {
+ throw new UnimplementedException("method:" + methodName);
+ }
+ }
+
+ MethodDescriptor generatedUnaryDescriptor =
findGeneratedUnaryMethodDescriptor(methodDescriptors);
+ if (generatedUnaryDescriptor != null) {
+ return generatedUnaryDescriptor;
+ }
+ throw new UnimplementedException("method:" + methodName);
+ }
+
+ private static MethodDescriptor findSingleOrGeneratedUnaryMethodDescriptor(
+ List<MethodDescriptor> methodDescriptors) {
+ if (CollectionUtils.isEmpty(methodDescriptors)) {
+ return null;
+ }
+ // In most cases there is only one method
+ if (methodDescriptors.size() == 1) {
+ return methodDescriptors.get(0);
+ }
+ return findGeneratedUnaryMethodDescriptor(methodDescriptors);
+ }
+
+ private static TripleRequestWrapper parseRequestWrapper(InputStream
rawMessage) throws IOException {
+ rawMessage.mark(Integer.MAX_VALUE);
+ try {
+ return TripleRequestWrapper.parseFrom(rawMessage);
+ } finally {
+ rawMessage.reset();
+ }
+ }
+
+ private static MethodDescriptor findWellKnownMethodDescriptor(String
methodName) {
if (isGeneric(methodName)) {
- // There should be one and only one
- methodDescriptor = ServiceDescriptorInternalCache.genericService()
+ return ServiceDescriptorInternalCache.genericService()
.getMethods(methodName)
.get(0);
- } else if (isEcho(methodName)) {
- // There should be one and only one
+ }
+ if (isEcho(methodName)) {
return ServiceDescriptorInternalCache.echoService()
.getMethods(methodName)
.get(0);
- } else {
- List<MethodDescriptor> methodDescriptors =
serviceDescriptor.getMethods(methodName);
- if (CollectionUtils.isEmpty(methodDescriptors)) {
- return null;
- }
- // In most cases there is only one method
- if (methodDescriptors.size() == 1) {
- methodDescriptor = methodDescriptors.get(0);
- }
- // generated unary method ,use unary type
- // Response foo(Request)
- // void foo(Request,StreamObserver<Response>)
- if (methodDescriptors.size() == 2) {
- if (methodDescriptors.get(1).getRpcType() ==
MethodDescriptor.RpcType.SERVER_STREAM) {
- methodDescriptor = methodDescriptors.get(0);
- } else if (methodDescriptors.get(0).getRpcType() ==
MethodDescriptor.RpcType.SERVER_STREAM) {
- methodDescriptor = methodDescriptors.get(1);
- }
- }
}
- return methodDescriptor;
+ return null;
}
- public static MethodDescriptor findTripleMethodDescriptor(
- ServiceDescriptor serviceDescriptor, String methodName,
InputStream rawMessage) throws IOException {
- MethodDescriptor methodDescriptor =
findReflectionMethodDescriptor(serviceDescriptor, methodName);
- if (methodDescriptor == null) {
- rawMessage.mark(Integer.MAX_VALUE);
- List<MethodDescriptor> methodDescriptors =
serviceDescriptor.getMethods(methodName);
- TripleRequestWrapper request =
TripleRequestWrapper.parseFrom(rawMessage);
- String[] paramTypes = request.getArgTypes().toArray(new String[0]);
+ private static MethodDescriptor findMethodDescriptorByParamTypes(
+ List<MethodDescriptor> methodDescriptors, String[] paramTypes) {
+ for (MethodDescriptor descriptor : methodDescriptors) {
// wrapper mode the method can overload so maybe list
- for (MethodDescriptor descriptor : methodDescriptors) {
- // params type is array
- if (Arrays.equals(descriptor.getCompatibleParamSignatures(),
paramTypes)) {
- methodDescriptor = descriptor;
- break;
- }
+ if (Arrays.equals(descriptor.getCompatibleParamSignatures(),
paramTypes)) {
+ return descriptor;
}
- if (methodDescriptor == null) {
- throw new UnimplementedException("method:" + methodName);
+ }
+ return null;
+ }
+
+ private static MethodDescriptor
findGeneratedUnaryMethodDescriptor(List<MethodDescriptor> methodDescriptors) {
+ // Generated unary methods may expose two Java methods for the same
RPC:
+ // Response foo(Request)
+ // void foo(Request, StreamObserver<Response>)
+ if (methodDescriptors.size() != 2) {
+ return null;
+ }
+
+ MethodDescriptor unaryMethodDescriptor = null;
+ MethodDescriptor serverStreamMethodDescriptor = null;
+ for (MethodDescriptor descriptor : methodDescriptors) {
+ if (descriptor.getRpcType() == MethodDescriptor.RpcType.UNARY) {
+ unaryMethodDescriptor = descriptor;
+ } else if (descriptor.getRpcType() ==
MethodDescriptor.RpcType.SERVER_STREAM) {
+ serverStreamMethodDescriptor = descriptor;
}
- rawMessage.reset();
}
- return methodDescriptor;
+ if (unaryMethodDescriptor == null || serverStreamMethodDescriptor ==
null) {
+ return null;
+ }
+ if (!Arrays.equals(
+ unaryMethodDescriptor.getParameterClasses(),
+ getServerStreamRequestTypes(serverStreamMethodDescriptor))) {
+ return null;
+ }
+ if (!isSameResponseType(unaryMethodDescriptor,
serverStreamMethodDescriptor)) {
+ return null;
+ }
+ return unaryMethodDescriptor;
+ }
+
+ private static Class<?>[] getServerStreamRequestTypes(MethodDescriptor
serverStreamMethodDescriptor) {
+ Class<?>[] parameterClasses =
serverStreamMethodDescriptor.getParameterClasses();
+ if (parameterClasses.length == 0
+ ||
!StreamObserver.class.isAssignableFrom(parameterClasses[parameterClasses.length
- 1])) {
+ return null;
+ }
+ return Arrays.copyOf(parameterClasses, parameterClasses.length - 1);
+ }
+
+ private static boolean isSameResponseType(
+ MethodDescriptor unaryMethodDescriptor, MethodDescriptor
serverStreamMethodDescriptor) {
+ Type[] returnTypes = unaryMethodDescriptor.getReturnTypes();
+ if (returnTypes.length == 0 || !(returnTypes[0] instanceof Class)) {
+ return false;
+ }
+ Class<?> actualResponseType =
serverStreamMethodDescriptor.getActualResponseType();
+ if (actualResponseType == null) {
+ return false;
+ }
+ return returnTypes[0].equals(actualResponseType);
}
private static boolean isGeneric(String methodName) {
diff --git
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtilsTest.java
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtilsTest.java
new file mode 100644
index 0000000000..d3c91523ef
--- /dev/null
+++
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtilsTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.remoting.http12.exception.UnimplementedException;
+import org.apache.dubbo.rpc.model.MethodDescriptor;
+import org.apache.dubbo.rpc.model.ReflectionServiceDescriptor;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class DescriptorUtilsTest {
+
+ @Test
+ void
shouldSelectServerStreamMethodByWrapperArgTypesWhenUnaryMethodHasSameName()
throws Exception {
+ ReflectionServiceDescriptor serviceDescriptor = new
ReflectionServiceDescriptor(OverloadedService.class);
+ TripleCustomerProtocolWrapper.TripleRequestWrapper request =
+
TripleCustomerProtocolWrapper.TripleRequestWrapper.Builder.newBuilder()
+ .setSerializeType("hessian4")
+ .addArgTypes(String.class.getName())
+ .addArgTypes(StreamObserver.class.getName())
+ .build();
+
+ MethodDescriptor methodDescriptor =
DescriptorUtils.findTripleMethodDescriptor(
+ serviceDescriptor, "sync", new
ByteArrayInputStream(request.toByteArray()));
+
+ assertEquals(MethodDescriptor.RpcType.SERVER_STREAM,
methodDescriptor.getRpcType());
+ }
+
+ @Test
+ void
shouldSelectUnaryMethodByWrapperArgTypesWhenServerStreamMethodHasSameName()
throws Exception {
+ ReflectionServiceDescriptor serviceDescriptor = new
ReflectionServiceDescriptor(OverloadedService.class);
+ TripleCustomerProtocolWrapper.TripleRequestWrapper request =
+
TripleCustomerProtocolWrapper.TripleRequestWrapper.Builder.newBuilder()
+ .setSerializeType("hessian4")
+ .addArgTypes(String.class.getName())
+ .build();
+
+ MethodDescriptor methodDescriptor =
DescriptorUtils.findTripleMethodDescriptor(
+ serviceDescriptor, "sync", new
ByteArrayInputStream(request.toByteArray()));
+
+ assertEquals(MethodDescriptor.RpcType.UNARY,
methodDescriptor.getRpcType());
+ }
+
+ @Test
+ void shouldSelectNoArgMethodByEmptyWrapperArgTypesWhenMethodIsOverloaded()
throws Exception {
+ ReflectionServiceDescriptor serviceDescriptor = new
ReflectionServiceDescriptor(NoArgOverloadedService.class);
+ TripleCustomerProtocolWrapper.TripleRequestWrapper request =
+
TripleCustomerProtocolWrapper.TripleRequestWrapper.Builder.newBuilder()
+ .setSerializeType("hessian4")
+ .build();
+
+ MethodDescriptor methodDescriptor =
DescriptorUtils.findTripleMethodDescriptor(
+ serviceDescriptor, "overload", new
ByteArrayInputStream(request.toByteArray()));
+
+ assertEquals(0, methodDescriptor.getParameterClasses().length);
+ }
+
+ @Test
+ void shouldRejectOverloadedMethodsWithoutMatchingWrapperArgTypes() {
+ ReflectionServiceDescriptor serviceDescriptor = new
ReflectionServiceDescriptor(OverloadedService.class);
+ TripleCustomerProtocolWrapper.TripleRequestWrapper request =
+
TripleCustomerProtocolWrapper.TripleRequestWrapper.Builder.newBuilder()
+ .setSerializeType("hessian4")
+ .build();
+
+ assertThrows(
+ UnimplementedException.class,
+ () -> DescriptorUtils.findTripleMethodDescriptor(
+ serviceDescriptor, "sync", new
ByteArrayInputStream(request.toByteArray())));
+ }
+
+ @Test
+ void shouldFallbackToUnaryMethodForGeneratedPairWithoutWrapperArgTypes()
throws Exception {
+ ReflectionServiceDescriptor serviceDescriptor = new
ReflectionServiceDescriptor(GeneratedUnaryService.class);
+ TripleCustomerProtocolWrapper.TripleRequestWrapper request =
+
TripleCustomerProtocolWrapper.TripleRequestWrapper.Builder.newBuilder()
+ .setSerializeType("hessian4")
+ .build();
+
+ MethodDescriptor methodDescriptor =
DescriptorUtils.findTripleMethodDescriptor(
+ serviceDescriptor, "generated", new
ByteArrayInputStream(request.toByteArray()));
+
+ assertEquals(MethodDescriptor.RpcType.UNARY,
methodDescriptor.getRpcType());
+ }
+
+ @Test
+ void shouldRejectAmbiguousTwoMethodReflectionOverload() {
+ ReflectionServiceDescriptor serviceDescriptor =
+ new
ReflectionServiceDescriptor(AmbiguousReflectionService.class);
+
+ assertThrows(
+ UnimplementedException.class,
+ () ->
DescriptorUtils.findReflectionMethodDescriptor(serviceDescriptor, "ambiguous"));
+ }
+
+ @Test
+ void shouldSurfaceCorruptWrapperForGeneratedPair() {
+ ReflectionServiceDescriptor serviceDescriptor = new
ReflectionServiceDescriptor(GeneratedUnaryService.class);
+ byte[] corruptWrapper = new byte[] {0x0A, 0x05, 'h', 'e'};
+
+ assertThrows(
+ IOException.class,
+ () -> DescriptorUtils.findTripleMethodDescriptor(
+ serviceDescriptor, "generated", new
ByteArrayInputStream(corruptWrapper)));
+ }
+
+ private interface OverloadedService {
+
+ DataWrapper<String> sync(String value);
+
+ void sync(String value, StreamObserver<String> response);
+ }
+
+ private interface NoArgOverloadedService {
+
+ String overload();
+
+ String overload(String value);
+ }
+
+ private interface GeneratedUnaryService {
+
+ String generated(String value);
+
+ void generated(String value, StreamObserver<String> response);
+ }
+
+ private interface AmbiguousReflectionService {
+
+ String ambiguous(String value);
+
+ void ambiguous(String value, StreamObserver<Integer> response);
+ }
+}