diff --git a/dubbo-common/pom.xml b/dubbo-common/pom.xml
index 0e8be699cd..1e2d18a83b 100644
--- a/dubbo-common/pom.xml
+++ b/dubbo-common/pom.xml
@@ -81,6 +81,20 @@
protobuf-java
test
+
+ io.projectreactor
+ reactor-core
+ 3.4.9
+ test
+
+
+ io.reactivex.rxjava2
+ rxjava
+ 2.2.21
+ test
+
+
+
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/MethodDescriptor.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/MethodDescriptor.java
index 8ce071abc1..ccb75b9412 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/MethodDescriptor.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/MethodDescriptor.java
@@ -25,6 +25,7 @@ import org.apache.dubbo.common.utils.ReflectUtils;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Stream;
@@ -38,6 +39,13 @@ import static org.apache.dubbo.common.constants.CommonConstants.PROTOBUF_MESSAGE
*
*/
public class MethodDescriptor {
+
+ private static final String GRPC_ASYNC_RETURN_CLASS = "com.google.common.util.concurrent.ListenableFuture";
+ private static final String TRI_ASYNC_RETURN_CLASS = "java.util.concurrent.CompletableFuture";
+ private static final String REACTOR_RETURN_CLASS = "reactor.core.publisher.Mono";
+ private static final String RX_RETURN_CLASS = "io.reactivex.Single";
+ private static final String GRPC_STREAM_CLASS = "io.grpc.stub.StreamObserver";
+
private static final Logger logger = LoggerFactory.getLogger(MethodDescriptor.class);
private final Method method;
// private final boolean isCallBack;
@@ -58,18 +66,22 @@ public class MethodDescriptor {
this.method = method;
this.methodName = method.getName();
Class>[] parameterTypes = method.getParameterTypes();
+ // bidirectional-stream: StreamObserver foo(StreamObserver)
if (parameterTypes.length == 1 && isStreamType(parameterTypes[0])) {
this.parameterClasses = new Class>[]{
(Class>) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0]};
this.returnClass = (Class>) ((ParameterizedType) method.getGenericParameterTypes()[0])
.getActualTypeArguments()[0];
this.rpcType = RpcType.BIDIRECTIONAL_STREAM;
+ // server-stream: void foo(Request, StreamObserver)
} else if (parameterTypes.length == 2 && method.getReturnType().equals(Void.TYPE)
&& !isStreamType(parameterTypes[0]) && isStreamType(parameterTypes[1])) {
this.parameterClasses = method.getParameterTypes();
- this.returnClass = (Class>) ((ParameterizedType)method.getGenericParameterTypes()[1]).getActualTypeArguments()[0];
+ this.returnClass =
+ (Class>) ((ParameterizedType) method.getGenericParameterTypes()[1]).getActualTypeArguments()[0];
this.rpcType = RpcType.SERVER_STREAM;
} else {
+ // unary: Response foo(Request)
this.parameterClasses = method.getParameterTypes();
this.returnClass = method.getReturnType();
this.rpcType = RpcType.UNARY;
@@ -92,7 +104,7 @@ public class MethodDescriptor {
}
private static boolean isStreamType(Class> clz) {
- return StreamObserver.class.isAssignableFrom(clz);
+ return StreamObserver.class.isAssignableFrom(clz) || GRPC_STREAM_CLASS.equalsIgnoreCase(clz.getName());
}
public boolean isStream() {
@@ -111,30 +123,156 @@ public class MethodDescriptor {
return rpcType;
}
+ /**
+ * Determine if the request and response instance should be wrapped in Protobuf wrapper object
+ *
+ * @return true if the request and response object is not generated by protobuf
+ */
private boolean needWrap() {
+ // generic call must be wrapped
if (CommonConstants.$INVOKE.equals(methodName) || CommonConstants.$INVOKE_ASYNC.equals(methodName)) {
return true;
- } else if ($ECHO.equals(methodName)) {
- return true;
- } else {
- if ((rpcType != RpcType.SERVER_STREAM && parameterClasses.length != 1) || parameterClasses[0] == null) {
- return true;
- }
-
- Class> clazz = parameterClasses[0];
- while (clazz != Object.class && clazz != null) {
- Class>[] interfaces = clazz.getInterfaces();
- if (interfaces.length > 0) {
- for (Class> clazzInterface : interfaces) {
- if (PROTOBUF_MESSAGE_CLASS_NAME.equalsIgnoreCase(clazzInterface.getName())) {
- return false;
- }
- }
- }
- clazz = clazz.getSuperclass();
- }
+ }
+ // echo must be wrapped
+ if ($ECHO.equals(methodName)) {
return true;
}
+ boolean returnClassProtobuf = isProtobufClass(returnClass);
+ // Response foo()
+ if (parameterClasses.length == 0) {
+ return !returnClassProtobuf;
+ }
+ int protobufParameterCount = 0;
+ int javaParameterCount = 0;
+ int streamParameterCount = 0;
+ boolean secondParameterStream = false;
+ // count normal and protobuf param
+ for (int i = 0; i < parameterClasses.length; i++) {
+ Class> parameterClass = parameterClasses[i];
+ if (isProtobufClass(parameterClass)) {
+ protobufParameterCount++;
+ } else {
+ if (isStreamType(parameterClass)) {
+ if (i == 1) {
+ secondParameterStream = true;
+ }
+ streamParameterCount++;
+ } else {
+ javaParameterCount++;
+ }
+ }
+ }
+ // more than one stream param
+ if (streamParameterCount > 1) {
+ throw new IllegalStateException("method params error: more than one Stream params. method=" + methodName);
+ }
+ // protobuf only support one param
+ if (protobufParameterCount >= 2) {
+ throw new IllegalStateException("method params error: more than one protobuf params. method=" + methodName);
+ }
+ // server stream support one normal param and one stream param
+ if (streamParameterCount == 1) {
+ if (javaParameterCount + protobufParameterCount > 1) {
+ throw new IllegalStateException("method params error: server stream does not support more than one normal param." +
+ " method=" + methodName);
+ }
+ // server stream: void foo(Request, StreamObserver)
+ if (!secondParameterStream) {
+ throw new IllegalStateException("method params error: server stream's second param must be StreamObserver." +
+ " method=" + methodName);
+ }
+ }
+ if (isStream()) {
+ if (RpcType.SERVER_STREAM == rpcType) {
+ if (!secondParameterStream) {
+ throw new IllegalStateException("method params error:server stream's second param must be StreamObserver." +
+ " method=" + methodName);
+ }
+ }
+ // param type must be consistent
+ if (returnClassProtobuf) {
+ if (javaParameterCount > 0) {
+ throw new IllegalStateException("method params error: both normal and protobuf param found. method=" + methodName);
+ }
+ } else {
+ if (protobufParameterCount > 0) {
+ throw new IllegalStateException("method params error method=" + methodName);
+ }
+ }
+ } else {
+ if (streamParameterCount > 0) {
+ throw new IllegalStateException("method params error: unary method should not contain any StreamObserver." +
+ " method=" + methodName);
+ }
+ if (protobufParameterCount > 0 && returnClassProtobuf) {
+ return false;
+ }
+ // handler reactor or rxjava only consider gen by proto
+ if (isMono(returnClass) || isRx(returnClass)) {
+ return false;
+ }
+ if (protobufParameterCount <= 0 && !returnClassProtobuf) {
+ return true;
+ }
+ // handle grpc stub only consider gen by proto
+ if (GRPC_ASYNC_RETURN_CLASS.equalsIgnoreCase(returnClass.getName()) && protobufParameterCount == 1) {
+ return false;
+ }
+ // handle dubbo generated method
+ if (TRI_ASYNC_RETURN_CLASS.equalsIgnoreCase(returnClass.getName())) {
+ Class> actualReturnClass =
+ (Class>) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0];
+ boolean actualReturnClassProtobuf = isProtobufClass(actualReturnClass);
+ if (actualReturnClassProtobuf && protobufParameterCount == 1) {
+ return false;
+ }
+ if (!actualReturnClassProtobuf && protobufParameterCount == 0) {
+ return true;
+ }
+ }
+ // todo remove this in future
+ boolean ignore = checkNeedIgnore();
+ if (ignore) {
+ return protobufParameterCount != 1;
+ }
+ throw new IllegalStateException("method params error method=" + methodName);
+ }
+ // java param should be wrapped
+ return javaParameterCount > 0;
+ }
+
+ /**
+ * fixme will produce error on grpc. but is harmless so ignore now
+ */
+ private boolean checkNeedIgnore() {
+ if (Iterator.class.isAssignableFrom(returnClass)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isMono(Class> clz) {
+ return REACTOR_RETURN_CLASS.equalsIgnoreCase(clz.getName());
+ }
+
+ private boolean isRx(Class> clz) {
+ return RX_RETURN_CLASS.equalsIgnoreCase(clz.getName());
+ }
+
+
+ public boolean isProtobufClass(Class> clazz) {
+ while (clazz != Object.class && clazz != null) {
+ Class>[] interfaces = clazz.getInterfaces();
+ if (interfaces.length > 0) {
+ for (Class> clazzInterface : interfaces) {
+ if (PROTOBUF_MESSAGE_CLASS_NAME.equalsIgnoreCase(clazzInterface.getName())) {
+ return true;
+ }
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+ return false;
}
public boolean matchParams(String params) {
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/descriptor/DescriptorService.java b/dubbo-common/src/test/java/org/apache/dubbo/descriptor/DescriptorService.java
index 98d2dc7b4d..a0117ce30b 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/descriptor/DescriptorService.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/descriptor/DescriptorService.java
@@ -23,6 +23,37 @@ public interface DescriptorService {
void noParameterMethod();
+ /**
+ * unray return protobuf class
+ *
+ * @return protobuf class
+ */
+ HelloReply noParameterAndReturnProtobufMethod();
+
+ /**
+ * unray return java class
+ *
+ * @return
+ */
+ String noParameterAndReturnJavaClassMethod();
+
+
+ /**
+ * bi stream need wrapper
+ *
+ * @param streamObserver
+ * @return
+ */
+ StreamObserver wrapBidirectionalStream(StreamObserver streamObserver);
+
+ /**
+ * no need wrapper bi stream
+ *
+ * @param streamObserver
+ * @return
+ */
+ StreamObserver bidirectionalStream(StreamObserver streamObserver);
+
/**
* only for test.
*
@@ -34,4 +65,38 @@ public interface DescriptorService {
void sayHelloServerStream(HelloReply request, StreamObserver reply);
void sayHelloServerStream2(Object request, StreamObserver