issue 5900:add bytebuddy support (#11117)
* issue 5900:add bytebuddy support * remove unused import * edit comment * rename CompositeMethodInvoker * add unit test * fix ut
This commit is contained in:
parent
fe4d99dfaa
commit
3c103d40cd
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@
|
|||
<!-- <spring_version>4.3.30.RELEASE</spring_version> -->
|
||||
<spring_version>5.2.20.RELEASE</spring_version>
|
||||
<javassist_version>3.28.0-GA</javassist_version>
|
||||
<bytebuddy.version>1.12.19</bytebuddy.version>
|
||||
<netty_version>3.2.5.Final</netty_version>
|
||||
<netty4_version>4.1.72.Final</netty4_version>
|
||||
<mina_version>2.1.5</mina_version>
|
||||
|
|
@ -213,6 +214,11 @@
|
|||
<artifactId>javassist</artifactId>
|
||||
<version>${javassist_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
<version>${bytebuddy.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.netty</groupId>
|
||||
<artifactId>netty</artifactId>
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@
|
|||
<artifactId>dubbo-remoting-api</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>hessian-lite</artifactId>
|
||||
|
|
|
|||
|
|
@ -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 <T> Invoker<T> getInvoker(T proxy, Class<T> 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<T> 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> T getProxy(Invoker<T> 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 <T> Invoker<T> doGetInvoker(T proxy, Class<T> type, URL url);
|
||||
|
||||
protected abstract <T> T doGetProxy(Invoker<T> invoker, Class<?>[] types);
|
||||
}
|
||||
|
|
@ -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<Method> 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<String, MethodInvoker> invokers;
|
||||
|
||||
public CompositeMethodInvoker(Map<String, MethodInvoker> invokers) {
|
||||
this.invokers = invokers;
|
||||
}
|
||||
|
||||
/**
|
||||
* for test
|
||||
*
|
||||
* @return all MethodInvoker
|
||||
*/
|
||||
Map<String, MethodInvoker> 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<String, MethodInvoker> invokers = new HashMap<>();
|
||||
Map<String, List<Method>> map = new HashMap<>();
|
||||
for (Method method : methods) {
|
||||
map.computeIfAbsent(method.getName(), name -> new ArrayList<>()).add(method);
|
||||
}
|
||||
Set<Map.Entry<String, List<Method>>> entries = map.entrySet();
|
||||
for (Map.Entry<String, List<Method>> entry : entries) {
|
||||
String methodName = entry.getKey();
|
||||
List<Method> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ClassLoader, Map<CacheKey, ByteBuddyProxy>> 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<CacheKey, ByteBuddyProxy> 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<ByteCodeElement> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <T> Invoker<T> doGetInvoker(T proxy, Class<T> type, URL url) {
|
||||
return ByteBuddyProxyInvoker.newInstance(proxy, type, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T doGetProxy(Invoker<T> invoker, Class<?>[] interfaces) {
|
||||
ClassLoader classLoader = invoker.getInterface().getClassLoader();
|
||||
return (T) ByteBuddyProxy.newInstance(classLoader, interfaces, new InvokerInvocationHandler(invoker));
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T> extends AbstractProxyInvoker<T> {
|
||||
|
||||
private final MethodInvoker methodInvoker;
|
||||
|
||||
private ByteBuddyProxyInvoker(T proxy,
|
||||
Class<T> 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 <T> ByteBuddyProxyInvoker<T> newInstance(T proxy, Class<T> type, URL url) {
|
||||
return new ByteBuddyProxyInvoker<>(proxy, type, url, MethodInvoker.newInstance(proxy.getClass()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
bytebuddy=org.apache.dubbo.rpc.proxy.bytebuddy.ByteBuddyProxyFactory
|
||||
nativestub=org.apache.dubbo.rpc.stub.StubProxyFactory
|
||||
|
|
|
|||
|
|
@ -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<String, MethodInvoker> 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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<RemoteService> 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"}));
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue