前言
上一篇講了Dubbo消費(fèi)端初始化的過(guò)程,在應(yīng)用啟動(dòng)時(shí),Dubbo會(huì)掃描classpath下的類,找到@Reference注解后注入生成的遠(yuǎn)程服務(wù)代理。這里面主要涉及到Invoker和Proxy的生成,這篇文章先分析下Proxy的生成過(guò)程。
代理工廠
Dubbo中代理實(shí)例是通過(guò)代理工廠來(lái)獲得的,代理工廠的接口定義如下:
@SPI("javassist")
public interface ProxyFactory {
@Adaptive({PROXY_KEY})
<T> T getProxy(Invoker<T> invoker) throws RpcException;
@Adaptive({PROXY_KEY})
<T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException;
@Adaptive({PROXY_KEY})
<T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
}
其中g(shù)etProxy()方法就是給消費(fèi)端使用來(lái)獲取代理實(shí)例的,可以看到要拿到一個(gè)Proxy實(shí)例需要提供一個(gè)Invoker,第二個(gè)方法中的generic參數(shù)是代表是否生成一個(gè)泛化接口的代理,關(guān)于泛化調(diào)用后面會(huì)有專門的文章來(lái)講解。最后一個(gè)getInvoker()是Provider端暴露服務(wù)的時(shí)候使用,這個(gè)講到服務(wù)提供方的時(shí)候再說(shuō)。
代理工廠實(shí)現(xiàn)
Dubbo提供了2種代理工廠的實(shí)現(xiàn),一種是JDK自帶的動(dòng)態(tài)代理,一種是使用javassist直接生成字節(jié)碼的實(shí)現(xiàn),默認(rèn)使用的是第二種。
JDK動(dòng)態(tài)代理
實(shí)現(xiàn)類是JdkProxyFactory,直接調(diào)用的Proxy.newProxyInstance().
public class JdkProxyFactory extends AbstractProxyFactory {
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new InvokerInvocationHandler(invoker));
}
}
這里面的interfaces參數(shù)是AbstractProxyFactory中添加的,除了包含invoker要調(diào)用的interface外,還會(huì)額外添加兩個(gè)接口EchoService和Destroyable,這兩個(gè)是Dubbo框架默認(rèn)會(huì)實(shí)現(xiàn)的接口。就是說(shuō)再所有返回的代理中,都額外實(shí)現(xiàn)了這兩個(gè)接口。
JDK代理的邏輯都是在InvokerInvocationHandler中實(shí)現(xiàn)的:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 0) {
if ("toString".equals(methodName)) {
return invoker.toString();
} else if ("$destroy".equals(methodName)) {
invoker.destroy();
return null;
} else if ("hashCode".equals(methodName)) {
return invoker.hashCode();
}
} else if (parameterTypes.length == 1 && "equals".equals(methodName)) {
return invoker.equals(args[0]);
}
RpcInvocation rpcInvocation = new RpcInvocation(method, invoker.getInterface().getName(), args);
String serviceKey = invoker.getUrl().getServiceKey();
rpcInvocation.setTargetServiceUniqueName(serviceKey);
if (consumerModel != null) {
rpcInvocation.put(Constants.CONSUMER_MODEL, consumerModel);
rpcInvocation.put(Constants.METHOD_MODEL, consumerModel.getMethodModel(method));
}
return invoker.invoke(rpcInvocation).recreate();
}
上面的邏輯比較簡(jiǎn)單,首先判斷下要調(diào)用的方法是否屬于Object類,或者是調(diào)用的toString()、$destroy()或hashCode()方法,如果是的話,就直接調(diào)用Invoker的本地方法,不會(huì)發(fā)起遠(yuǎn)程調(diào)用。否則,將請(qǐng)求參數(shù)封裝至·RpcInvocation·中,通過(guò)Invoker發(fā)送出去。
Javassist動(dòng)態(tài)代理
Javassist是一個(gè)字節(jié)碼生成工具,它可以在程序運(yùn)行期間動(dòng)態(tài)編譯源代碼生成class的字節(jié)碼。Dubbo加載這些類的字節(jié)碼,然后發(fā)起調(diào)用。說(shuō)簡(jiǎn)單點(diǎn)就是,Dubbo為每個(gè)遠(yuǎn)程接口都生成一份源代碼并編譯,在請(qǐng)求時(shí)直接調(diào)用這些編譯好的類的方法就可以了,這樣顯然比動(dòng)態(tài)代理效率來(lái)的高。
public class JavassistProxyFactory extends AbstractProxyFactory {
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
}
代理工廠實(shí)現(xiàn)類里直接調(diào)用的·Proxy.getProxy()·獲取一個(gè)proxy的class,然后通過(guò)傳入InvokerInvocationHandler參數(shù)調(diào)用newInstance()方法。這個(gè)參數(shù)跟JDK動(dòng)態(tài)代理回調(diào)的handler是同一個(gè)。所以這里的核心就是Proxy的class是怎么產(chǎn)生和加載的。注意這里用的Proxy類不是JDK那個(gè),這個(gè)是Dubbo定義的。
public static Proxy getProxy(ClassLoader cl, Class<?>... ics) {
...
...
//參數(shù)校驗(yàn)的邏輯省略
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ics.length; i++) {
String itf = ics[i].getName();
...
...
sb.append(itf).append(';');
}
// key是所有要實(shí)現(xiàn)的接口用分號(hào)連在一起
String key = sb.toString();
// 查看緩存中是否已經(jīng)生成過(guò)這個(gè)代理,如果生成過(guò)直接返回,如果生成中則等待
final Map<String, Object> cache;
synchronized (PROXY_CACHE_MAP) {
cache = PROXY_CACHE_MAP.computeIfAbsent(cl, k -> new HashMap<>());
}
Proxy proxy = null;
synchronized (cache) {
do {
Object value = cache.get(key);
if (value instanceof Reference<?>) {
proxy = (Proxy) ((Reference<?>) value).get();
if (proxy != null) {
return proxy;
}
}
if (value == PENDING_GENERATION_MARKER) {
try {
cache.wait();
} catch (InterruptedException e) {
}
} else {
cache.put(key, PENDING_GENERATION_MARKER);
break;
}
}
while (true);
}
long id = PROXY_CLASS_COUNTER.getAndIncrement();
String pkg = null;
ClassGenerator ccp = null, ccm = null;
try {
// Java類源代碼生成器,封裝了javaassist
ccp = ClassGenerator.newInstance(cl);
Set<String> worked = new HashSet<>();
List<Method> methods = new ArrayList<>();
for (int i = 0; i < ics.length; i++) {
if (!Modifier.isPublic(ics[i].getModifiers())) {
String npkg = ics[i].getPackage().getName();
if (pkg == null) {
pkg = npkg;
} else {
if (!pkg.equals(npkg)) {
throw new IllegalArgumentException("non-public interfaces from different packages");
}
}
}
//添加類要實(shí)現(xiàn)的接口
ccp.addInterface(ics[i]);
//添加類要實(shí)現(xiàn)的方法
for (Method method : ics[i].getMethods()) {
String desc = ReflectUtils.getDesc(method);
if (worked.contains(desc) || Modifier.isStatic(method.getModifiers())) {
continue;
}
if (ics[i].isInterface() && Modifier.isStatic(method.getModifiers())) {
continue;
}
worked.add(desc);
int ix = methods.size();
Class<?> rt = method.getReturnType();
Class<?>[] pts = method.getParameterTypes();
StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");
for (int j = 0; j < pts.length; j++) {
code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";");
}
code.append(" Object ret = handler.invoke(this, methods[").append(ix).append("], args);");
if (!Void.TYPE.equals(rt)) {
code.append(" return ").append(asArgument(rt, "ret")).append(";");
}
methods.add(method);
ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());
}
}
if (pkg == null) {
pkg = PACKAGE_NAME;
}
// 添加構(gòu)造函數(shù)和成員變量
String pcn = pkg + ".proxy" + id;
ccp.setClassName(pcn);
ccp.addField("public static java.lang.reflect.Method[] methods;");
ccp.addField("private " + InvocationHandler.class.getName() + " handler;");
ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{InvocationHandler.class}, new Class<?>[0], "handler=$1;");
ccp.addDefaultConstructor();
Class<?> clazz = ccp.toClass();
clazz.getField("methods").set(null, methods.toArray(new Method[0]));
// 創(chuàng)建Proxy的子類,覆蓋newInstance()方法
String fcn = Proxy.class.getName() + id;
ccm = ClassGenerator.newInstance(cl);
ccm.setClassName(fcn);
ccm.addDefaultConstructor();
ccm.setSuperClass(Proxy.class);
ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");
Class<?> pc = ccm.toClass();
proxy = (Proxy) pc.newInstance();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
// release ClassGenerator
if (ccp != null) {
ccp.release();
}
if (ccm != null) {
ccm.release();
}
synchronized (cache) {
if (proxy == null) {
cache.remove(key);
} else {
cache.put(key, new WeakReference<Proxy>(proxy));
}
cache.notifyAll();
}
}
return proxy;
}
上面代碼中參數(shù)校驗(yàn)和緩存部分我們跳過(guò),直接看到后面實(shí)際上是使用了ClassGenerator生成了兩個(gè)類,一個(gè)是Proxy的子類,它只有一個(gè)newInstance()的方法,接收一個(gè)InvocationHandler的參數(shù),這個(gè)方法返回的就是真正的接口實(shí)現(xiàn)類。具體源代碼生成和編譯部分的代碼就不看了,實(shí)際是就是字符串的拼接然后用javaassist進(jìn)行編譯并加載。
下面看下Dubbo的Demo中的遠(yuǎn)程接口,通過(guò)javassist生成的代理類的源代碼是個(gè)什么樣子的。
源接口:
public interface DemoService {
String sayHello(String name);
default CompletableFuture<String> sayHelloAsync(String name) {
return CompletableFuture.completedFuture(sayHello(name));
}
}
生成的實(shí)現(xiàn)類源代碼(為了便于查看,做了一下format):
public class org.apache.dubbo.common.bytecode.Proxy0 extends org.apache.dubbo.common.bytecode.Proxy {
public Object newInstance(java.lang.reflect.InvocationHandler h){
return new org.apache.dubbo.common.bytecode.proxy0(h);
}
}
public class org.apache.dubbo.common.bytecode.proxy0 implements Destroyable, EchoService, DemoService{
public static java.lang.reflect.Method[] methods;
private java.lang.reflect.InvocationHandler handler;
public proxy0(java.lang.reflect.InvocationHandler arg0){
handler = arg0;
}
public void $destroy(){
Object[] args = new Object[0];
handler.invoke(this, methods[0], args);
}
public String sayHello(String arg0){
Object[] args = new Object[1];
args[0] = arg0;
return handler.invoke(this, methods[1], args);
}
public CompletableFuture sayHelloAsync(String arg0){
Object[] args = new Object[1];
args[0] = arg0;
return handler.invoke(this, methods[2], args);
}
public Object $echo(Object arg0){
Object[] args = new Object[1];
args[0] = ($w)$1;
return handler.invoke(this, methods[3], args);
}
}
從生成的源碼可以看到j(luò)avassist代理工廠實(shí)際上就是生成一個(gè)封裝類,所有的方法調(diào)用都是通過(guò)調(diào)用handler來(lái)實(shí)現(xiàn)的,這種方式是Dubbo默認(rèn)的生成代理的方式。
本地存根
有時(shí)候我們想在調(diào)用Proxy前后做一些事情,比如記錄日志,異常捕獲等,這個(gè)時(shí)候就會(huì)用到Dubbo的Stub。
首先看下官方文檔對(duì)本地存根的描述:
改成遠(yuǎn)程服務(wù)調(diào)用后,客戶端通常只剩下接口,而實(shí)現(xiàn)全在服務(wù)器端,但提供方有些時(shí)候想在客戶端也執(zhí)行部分邏輯,比如:做 ThreadLocal 緩存,提前驗(yàn)證參數(shù),調(diào)用失敗后偽造容錯(cuò)數(shù)據(jù)等等,此時(shí)就需要在 API 中帶上 Stub,客戶端生成 Proxy 實(shí)例,會(huì)把 Proxy 通過(guò)構(gòu)造函數(shù)傳給 Stub [1],然后把 Stub 暴露給用戶,Stub 可以決定要不要去調(diào) Proxy。
一個(gè)Stub的Demo:
package com.foo;
public class BarServiceStub implements BarService {
private final BarService barService;
// 構(gòu)造函數(shù)傳入真正的遠(yuǎn)程代理對(duì)象
public BarServiceStub(BarService barService){
this.barService = barService;
}
public String sayHello(String name) {
// 此代碼在客戶端執(zhí)行, 你可以在客戶端做ThreadLocal本地緩存,或預(yù)先驗(yàn)證參數(shù)是否合法,等等
try {
return barService.sayHello(name);
} catch (Exception e) {
// 你可以容錯(cuò),可以做任何AOP攔截事項(xiàng)
return "容錯(cuò)數(shù)據(jù)";
}
}
}
實(shí)際上stub就是一個(gè)代理模式的實(shí)現(xiàn),在dubbo調(diào)用到達(dá)獲取Proxy這一步的時(shí)候,不是直接返回proxy,而是把proxy實(shí)例給到用戶自定義的stub類。所以Dubbo要求Stub必須要有一個(gè)入?yún)⑹菍?duì)應(yīng)接口的構(gòu)造函數(shù)。這樣最終ProxyFactory返回的是用戶自定義的Stub,用戶在Stub中決定在調(diào)用Proxy的前后做一些自定義操作。用戶通過(guò)@Reference注解的stub屬性來(lái)設(shè)置Stub類的名字。
總結(jié)
Dubbo消費(fèi)端生成Proxy的邏輯還是比較簡(jiǎn)單的,下一篇會(huì)詳細(xì)解析下消費(fèi)端Invoker的構(gòu)造過(guò)程。