diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoggerCodeConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoggerCodeConstants.java index c0c819cade..ba0f29a2f5 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoggerCodeConstants.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/LoggerCodeConstants.java @@ -212,6 +212,8 @@ public interface LoggerCodeConstants { String PROXY_TIMEOUT_RESPONSE = "3-7"; + String PROXY_FAILED = "3-8"; + // protocol module String PROTOCOL_UNSUPPORTED = "4-1"; diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml index 8c7458fc40..0128f7852b 100644 --- a/dubbo-dependencies-bom/pom.xml +++ b/dubbo-dependencies-bom/pom.xml @@ -93,6 +93,7 @@ 5.2.20.RELEASE 3.28.0-GA + 1.12.19 3.2.5.Final 4.1.72.Final 2.1.5 @@ -213,6 +214,11 @@ javassist ${javassist_version} + + net.bytebuddy + byte-buddy + ${bytebuddy.version} + org.jboss.netty netty diff --git a/dubbo-rpc/dubbo-rpc-api/pom.xml b/dubbo-rpc/dubbo-rpc-api/pom.xml index e92329b7ed..c2fa751743 100644 --- a/dubbo-rpc/dubbo-rpc-api/pom.xml +++ b/dubbo-rpc/dubbo-rpc-api/pom.xml @@ -45,6 +45,10 @@ dubbo-remoting-api ${project.parent.version} + + net.bytebuddy + byte-buddy + com.alibaba hessian-lite diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/AbstractFallbackJdkProxyFactory.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/AbstractFallbackJdkProxyFactory.java new file mode 100644 index 0000000000..e48d12ea78 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/AbstractFallbackJdkProxyFactory.java @@ -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. + */ +package org.apache.dubbo.rpc.proxy; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.proxy.jdk.JdkProxyFactory; + +import java.util.Arrays; + +import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROXY_FAILED; + +public abstract class AbstractFallbackJdkProxyFactory extends AbstractProxyFactory { + + protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(this.getClass()); + + private final JdkProxyFactory jdkProxyFactory = new JdkProxyFactory(); + + @Override + public Invoker getInvoker(T proxy, Class type, URL url) throws RpcException { + try { + return doGetInvoker(proxy, type, url); + } catch (Throwable throwable) { + // try fall back to JDK proxy factory + String factoryName = getClass().getSimpleName(); + try { + Invoker invoker = jdkProxyFactory.getInvoker(proxy, type, url); + logger.error(PROXY_FAILED, "", "", "Failed to generate invoker by " + factoryName + " failed. Fallback to use JDK proxy success. " + + "Interfaces: " + type, throwable); + // log out error + return invoker; + } catch (Throwable fromJdk) { + logger.error(PROXY_FAILED, "", "", "Failed to generate invoker by " + factoryName + " failed. Fallback to use JDK proxy is also failed. " + + "Interfaces: " + type + " Javassist Error.", throwable); + logger.error(PROXY_FAILED, "", "", "Failed to generate invoker by " + factoryName + " failed. Fallback to use JDK proxy is also failed. " + + "Interfaces: " + type + " JDK Error.", fromJdk); + throw throwable; + } + } + } + + @Override + public T getProxy(Invoker invoker, Class[] interfaces) { + try { + return doGetProxy(invoker, interfaces); + } catch (Throwable throwable) { + // try fall back to JDK proxy factory + String factoryName = getClass().getSimpleName(); + try { + T proxy = jdkProxyFactory.getProxy(invoker, interfaces); + logger.error(PROXY_FAILED, "", "", "Failed to generate proxy by " + factoryName + " failed. Fallback to use JDK proxy success. " + + "Interfaces: " + Arrays.toString(interfaces), throwable); + return proxy; + } catch (Throwable fromJdk) { + logger.error(PROXY_FAILED, "", "", "Failed to generate proxy by " + factoryName + " failed. Fallback to use JDK proxy is also failed. " + + "Interfaces: " + Arrays.toString(interfaces) + " Javassist Error.", throwable); + logger.error(PROXY_FAILED, "", "", "Failed to generate proxy by " + factoryName + " failed. Fallback to use JDK proxy is also failed. " + + "Interfaces: " + Arrays.toString(interfaces) + " JDK Error.", fromJdk); + throw throwable; + } + } + } + + protected abstract Invoker doGetInvoker(T proxy, Class type, URL url); + + protected abstract T doGetProxy(Invoker invoker, Class[] types); +} diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/MethodInvoker.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/MethodInvoker.java new file mode 100644 index 0000000000..8f7df1f86a --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/MethodInvoker.java @@ -0,0 +1,136 @@ +/* + * 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.proxy; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface MethodInvoker { + + Object invoke(Object instance, String methodName, Class[] parameterTypes, Object[] arguments) throws Throwable; + + /** + * no overload method invoker + */ + class SingleMethodInvoker implements MethodInvoker { + + private final Method method; + + SingleMethodInvoker(Method method) { + this.method = method; + } + + @Override + public Object invoke(Object instance, String methodName, Class[] parameterTypes, Object[] arguments) throws Throwable { + return method.invoke(instance, arguments); + } + } + + /** + * overload method invoker + */ + class OverloadMethodInvoker implements MethodInvoker { + + private final MethodMeta[] methods; + + OverloadMethodInvoker(List methods) { + this.methods = methods.stream().map(MethodMeta::new).toArray(MethodMeta[]::new); + } + + @Override + public Object invoke(Object instance, String methodName, Class[] parameterTypes, Object[] arguments) throws Throwable { + for (MethodMeta meta : methods) { + if (Arrays.equals(meta.getParametersType(), parameterTypes)) { + return meta.getMethod().invoke(instance, arguments); + } + } + throw new NoSuchMethodException(instance.getClass().getName() + "." + methodName + Arrays.toString(parameterTypes)); + } + + private static class MethodMeta { + + private final Method method; + + private final Class[] parametersType; + + private MethodMeta(Method method) { + this.method = method; + this.parametersType = method.getParameterTypes(); + } + + public Method getMethod() { + return method; + } + + public Class[] getParametersType() { + return parametersType; + } + } + } + + class CompositeMethodInvoker implements MethodInvoker { + + private final Map invokers; + + public CompositeMethodInvoker(Map invokers) { + this.invokers = invokers; + } + + /** + * for test + * + * @return all MethodInvoker + */ + Map getInvokers() { + return invokers; + } + + @Override + public Object invoke(Object instance, String methodName, Class[] parameterTypes, Object[] arguments) throws Throwable { + MethodInvoker invoker = invokers.get(methodName); + if (invoker == null) { + throw new NoSuchMethodException(instance.getClass().getName() + "." + methodName + Arrays.toString(parameterTypes)); + } + return invoker.invoke(instance, methodName, parameterTypes, arguments); + } + } + + static MethodInvoker newInstance(Class clazz) { + Method[] methods = clazz.getMethods(); + Map invokers = new HashMap<>(); + Map> map = new HashMap<>(); + for (Method method : methods) { + map.computeIfAbsent(method.getName(), name -> new ArrayList<>()).add(method); + } + Set>> entries = map.entrySet(); + for (Map.Entry> entry : entries) { + String methodName = entry.getKey(); + List ms = entry.getValue(); + if (ms.size() == 1) { + invokers.put(methodName, new SingleMethodInvoker(ms.get(0))); + continue; + } + invokers.put(methodName, new OverloadMethodInvoker(ms)); + } + return new CompositeMethodInvoker(invokers); + } +} diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyInterceptor.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyInterceptor.java new file mode 100644 index 0000000000..6670af89b8 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyInterceptor.java @@ -0,0 +1,40 @@ +/* + * 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.proxy.bytebuddy; + +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.This; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +public class ByteBuddyInterceptor { + + private final InvocationHandler handler; + + ByteBuddyInterceptor(InvocationHandler handler) { + this.handler = handler; + } + + @RuntimeType + public Object intercept(@This Object obj, @AllArguments Object[] allArguments, + @Origin Method method) throws Throwable { + return handler.invoke(obj, method, allArguments); + } +} diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxy.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxy.java new file mode 100644 index 0000000000..ccc25b9df7 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxy.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.rpc.proxy.bytebuddy; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.description.ByteCodeElement; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; + +import static org.apache.dubbo.common.constants.CommonConstants.MAX_PROXY_COUNT; + +public class ByteBuddyProxy { + + private static final Map> PROXY_CACHE_MAP = new WeakHashMap<>(); + + private final Class proxyClass; + + private final InvocationHandler handler; + + private ByteBuddyProxy(Class proxyClass, InvocationHandler handler) { + this.proxyClass = proxyClass; + this.handler = handler; + } + + public static Object newInstance(ClassLoader cl, Class[] interfaces, InvocationHandler handler) { + return getProxy(cl, interfaces, handler).newInstance(); + } + + private static ByteBuddyProxy getProxy(ClassLoader cl, Class[] interfaces, InvocationHandler handler) { + if (interfaces.length > MAX_PROXY_COUNT) { + throw new IllegalArgumentException("interface limit exceeded"); + } + interfaces = interfaces.clone(); + Arrays.sort(interfaces, Comparator.comparing(Class::getName)); + CacheKey key = new CacheKey(interfaces); + // get cache by class loader. + final Map cache; + synchronized (PROXY_CACHE_MAP) { + cache = PROXY_CACHE_MAP.computeIfAbsent(cl, k -> new ConcurrentHashMap<>()); + } + + ByteBuddyProxy proxy = cache.get(key); + if (proxy == null) { + synchronized (interfaces[0]) { + proxy = cache.get(key); + if (proxy == null) { + // create ByteBuddyProxy class. + proxy = new ByteBuddyProxy(buildProxyClass(cl, interfaces, handler), handler); + cache.put(key, proxy); + } + } + } + return proxy; + } + + private Object newInstance() { + try { + Constructor constructor = proxyClass.getDeclaredConstructor(InvocationHandler.class); + return constructor.newInstance(handler); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + private static Class buildProxyClass(ClassLoader cl, Class[] ics, InvocationHandler handler) { + ElementMatcher.Junction methodMatcher = Arrays.stream(ics) + .map(ElementMatchers::isDeclaredBy).reduce(ElementMatcher.Junction::or) + .orElse(ElementMatchers.none()).and(ElementMatchers + .not(ElementMatchers.isDeclaredBy(Object.class))); + return new ByteBuddy() + .subclass(Proxy.class) + .implement(ics) + .method(methodMatcher) + .intercept(MethodDelegation.to(new ByteBuddyInterceptor(handler))) + .make() + .load(cl) + .getLoaded(); + } + + private static class CacheKey { + + private final Class[] classes; + + private CacheKey(Class[] classes) { + this.classes = classes; + } + + public Class[] getClasses() { + return classes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CacheKey that = (CacheKey) o; + return Arrays.equals(classes, that.classes); + } + + @Override + public int hashCode() { + return Arrays.hashCode(classes); + } + } +} diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyFactory.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyFactory.java new file mode 100644 index 0000000000..7f34686647 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyFactory.java @@ -0,0 +1,40 @@ +/* + * 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.proxy.bytebuddy; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.proxy.AbstractFallbackJdkProxyFactory; +import org.apache.dubbo.rpc.proxy.InvokerInvocationHandler; + +/** + * ByteBuddyRpcProxyFactory + */ +public class ByteBuddyProxyFactory extends AbstractFallbackJdkProxyFactory { + + @Override + protected Invoker doGetInvoker(T proxy, Class type, URL url) { + return ByteBuddyProxyInvoker.newInstance(proxy, type, url); + } + + @Override + @SuppressWarnings("unchecked") + protected T doGetProxy(Invoker invoker, Class[] interfaces) { + ClassLoader classLoader = invoker.getInterface().getClassLoader(); + return (T) ByteBuddyProxy.newInstance(classLoader, interfaces, new InvokerInvocationHandler(invoker)); + } +} diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyInvoker.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyInvoker.java new file mode 100644 index 0000000000..2f5761c39e --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyInvoker.java @@ -0,0 +1,58 @@ +/* + * 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.proxy.bytebuddy; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.proxy.AbstractProxyInvoker; +import org.apache.dubbo.rpc.proxy.MethodInvoker; + +class ByteBuddyProxyInvoker extends AbstractProxyInvoker { + + private final MethodInvoker methodInvoker; + + private ByteBuddyProxyInvoker(T proxy, + Class type, + URL url, + MethodInvoker methodInvoker) { + super(proxy, type, url); + this.methodInvoker = methodInvoker; + } + + @Override + protected Object doInvoke(T instance, String methodName, Class[] parameterTypes, Object[] arguments) throws Throwable { + if ("getClass".equals(methodName)) { + return instance.getClass(); + } + if ("hashCode".equals(methodName)) { + return instance.hashCode(); + } + if ("toString".equals(methodName)) { + return instance.toString(); + } + if ("equals".equals(methodName)) { + if (arguments.length == 1) { + return instance.equals(arguments[0]); + } + throw new IllegalArgumentException("Invoke method [" + methodName + "] argument number error."); + } + return methodInvoker.invoke(instance, methodName, parameterTypes, arguments); + } + + static ByteBuddyProxyInvoker newInstance(T proxy, Class type, URL url) { + return new ByteBuddyProxyInvoker<>(proxy, type, url, MethodInvoker.newInstance(proxy.getClass())); + } +} diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.ProxyFactory b/dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.ProxyFactory index 900b54fb5f..6ad3db9043 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.ProxyFactory +++ b/dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.ProxyFactory @@ -1,4 +1,5 @@ stub=org.apache.dubbo.rpc.proxy.wrapper.StubProxyFactoryWrapper jdk=org.apache.dubbo.rpc.proxy.jdk.JdkProxyFactory javassist=org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory -nativestub=org.apache.dubbo.rpc.stub.StubProxyFactory \ No newline at end of file +bytebuddy=org.apache.dubbo.rpc.proxy.bytebuddy.ByteBuddyProxyFactory +nativestub=org.apache.dubbo.rpc.stub.StubProxyFactory diff --git a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/MethodInvokerTest.java b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/MethodInvokerTest.java new file mode 100644 index 0000000000..e2be1938d2 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/MethodInvokerTest.java @@ -0,0 +1,53 @@ +/* + * 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.proxy; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +class MethodInvokerTest { + + @Test + void testNewInstance() throws Throwable { + String singleMethodName = "getThreadName"; + String overloadMethodName = "sayHello"; + + MethodInvoker methodInvoker = MethodInvoker.newInstance(RemoteServiceImpl.class); + assertInstanceOf(MethodInvoker.CompositeMethodInvoker.class, methodInvoker); + MethodInvoker.CompositeMethodInvoker compositeMethodInvoker = (MethodInvoker.CompositeMethodInvoker) methodInvoker; + Map invokers = compositeMethodInvoker.getInvokers(); + MethodInvoker getThreadNameMethodInvoker = invokers.get(singleMethodName); + assertInstanceOf(MethodInvoker.SingleMethodInvoker.class, getThreadNameMethodInvoker); + MethodInvoker sayHelloMethodInvoker = invokers.get(overloadMethodName); + assertInstanceOf(MethodInvoker.OverloadMethodInvoker.class, sayHelloMethodInvoker); + + RemoteServiceImpl remoteService = Mockito.mock(RemoteServiceImpl.class); + //invoke success, SingleMethodInvoker does not check parameter types + methodInvoker.invoke(remoteService, singleMethodName, new Class[0], new Object[0]); + methodInvoker.invoke(remoteService, singleMethodName, new Class[]{Object.class, Object.class, Object.class}, new Object[0]); + Mockito.verify(remoteService, Mockito.times(2)).getThreadName(); + + methodInvoker.invoke(remoteService, overloadMethodName, new Class[]{String.class}, new Object[]{"Hello arg1"}); + Mockito.verify(remoteService, Mockito.times(1)).sayHello("Hello arg1"); + methodInvoker.invoke(remoteService, overloadMethodName, new Class[]{String.class, String.class}, new Object[]{"Hello arg1", "Hello arg2"}); + Mockito.verify(remoteService, Mockito.times(1)).sayHello("Hello arg1", "Hello arg2"); + } +} diff --git a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/RemoteServiceImpl.java b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/RemoteServiceImpl.java index 50b82b1997..54b3420ba2 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/RemoteServiceImpl.java +++ b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/RemoteServiceImpl.java @@ -29,4 +29,8 @@ public class RemoteServiceImpl implements RemoteService { public String sayHello(String name) throws RemoteException { return "hello " + name + "@" + RemoteServiceImpl.class.getName(); } + + public String sayHello(String name, String arg2) { + return "hello " + name + "@" + RemoteServiceImpl.class.getName() + ", arg2 " + arg2; + } } diff --git a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyInterceptorTest.java b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyInterceptorTest.java new file mode 100644 index 0000000000..1ac609d3c9 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyInterceptorTest.java @@ -0,0 +1,45 @@ +/* + * 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.proxy.bytebuddy; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +class ByteBuddyInterceptorTest { + + @AfterEach + public void after(){ + Mockito.clearAllCaches(); + } + + @Test + void testIntercept() throws Throwable { + InvocationHandler handler = Mockito.mock(InvocationHandler.class); + ByteBuddyInterceptor interceptor = new ByteBuddyInterceptor(handler); + Method method = Mockito.mock(Method.class); + Proxy proxy = Mockito.mock(Proxy.class); + Object[] args = new Object[0]; + interceptor.intercept(proxy, args, method); + //'intercept' method will call 'invoke' method directly + Mockito.verify(handler, Mockito.times(1)).invoke(proxy, method, args); + } +} diff --git a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyFactoryTest.java b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyFactoryTest.java new file mode 100644 index 0000000000..312c09c9c9 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyFactoryTest.java @@ -0,0 +1,27 @@ +/* + * 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.proxy.bytebuddy; + +import org.apache.dubbo.rpc.proxy.AbstractProxyTest; + +class ByteBuddyProxyFactoryTest extends AbstractProxyTest { + + static { + factory = new ByteBuddyProxyFactory(); + } + +} diff --git a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyInvokerTest.java b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyInvokerTest.java new file mode 100644 index 0000000000..2a11557295 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyInvokerTest.java @@ -0,0 +1,38 @@ +/* + * 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.proxy.bytebuddy; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.proxy.RemoteService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class ByteBuddyProxyInvokerTest { + + @Test + void testNewInstance() throws Throwable { + URL url = URL.valueOf("test://test:11/test?group=dubbo&version=1.1"); + RemoteService proxy = Mockito.mock(RemoteService.class); + ByteBuddyProxyInvoker invoker = ByteBuddyProxyInvoker.newInstance(proxy, RemoteService.class, url); + invoker.doInvoke(proxy, "sayHello", new Class[]{String.class}, new Object[]{"test"}); + Mockito.verify(proxy, Mockito.times(1)).sayHello("test"); + + Assertions.assertThrows(IllegalArgumentException.class, + () -> invoker.doInvoke(proxy, "equals", new Class[]{String.class}, new Object[]{"test", "test2"})); + } +} diff --git a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyTest.java b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyTest.java new file mode 100644 index 0000000000..1e2295a0b8 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/proxy/bytebuddy/ByteBuddyProxyTest.java @@ -0,0 +1,43 @@ +/* + * 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.proxy.bytebuddy; + +import org.apache.dubbo.rpc.proxy.InvokerInvocationHandler; +import org.apache.dubbo.rpc.proxy.RemoteService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.lang.reflect.Proxy; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.mockito.ArgumentMatchers.any; + +class ByteBuddyProxyTest { + + @Test + void testNewInstance() throws Throwable { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + InvokerInvocationHandler handler = Mockito.mock(InvokerInvocationHandler.class); + Object proxy = ByteBuddyProxy.newInstance(cl, new Class[]{RemoteService.class}, handler); + assertInstanceOf(RemoteService.class, proxy); + assertInstanceOf(Proxy.class, proxy); + RemoteService remoteService = (RemoteService) proxy; + remoteService.getThreadName(); + remoteService.sayHello("test"); + Mockito.verify(handler, Mockito.times(2)).invoke(any(), any(), any()); + } +}