原創/朱季謙
閱讀Dubbo原始碼過程中,會發現,Dubbo消費端在做遠端呼叫時,預設通過 Javassist 框架為服務介面生成動態代理類,呼叫javassist框架下的JavassistProxyFactory類的getProxy(Invoker
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
那麼,問題來了,如果我們想要一睹該動態生成的代理類內部結構是怎樣的,如何才能便捷做到的?
這就是我想介紹的一款工具,它可以幫助我們檢視JDK或者javassist生成的動態代理類,當然,它的功能遠不止此,還可以在生產環境進行診斷。
Arthas 是Alibaba開源的Java診斷工具,官方線上文件地址:https://arthas.aliyun.com/doc/
根據官網上的介紹,它還可以解決以下問題————
當你遇到以下類似問題而束手無策時,Arthas可以幫助你解決:
這個類從哪個 jar 包載入的?為什麼會報各種類相關的 Exception?
我改的程式碼為什麼沒有執行到?難道是我沒 commit?分支搞錯了?
遇到問題無法線上上 debug,難道只能通過加日誌再重新發布嗎?
線上遇到某個使用者的資料處理有問題,但線上同樣無法 debug,線下無法重現!
是否有一個全域性視角來檢視系統的執行狀況?
有什麼辦法可以監控到JVM的實時執行狀態?
怎麼快速定位應用的熱點,生成火焰圖?
怎樣直接從JVM內查詢某個類的例項?
這些方案本文暫不展開,這裡只展開通過該工具檢視Dubbo生成的動態代理類。
我是直接在使用dubbo-parent原始碼中的例子,分別啟動了提供者與消費者。
首先,啟動提供者方法——
public class Application {
public static void main(String[] args) throws Exception {
startWithBootstrap();
}
private static boolean isClassic(String[] args) {
return args.length > 0 && "classic".equalsIgnoreCase(args[0]);
}
private static void startWithBootstrap() {
ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
DubboBootstrap bootstrap = DubboBootstrap.getInstance();
RegistryConfig registryConfig = new RegistryConfig("zookeeper://127.0.0.1:2181");
registryConfig.setTimeout(20000);
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setHost("192.168.100.1");
protocolConfig.setPort(20877);
bootstrap.application(new ApplicationConfig("dubbo-demo-api-provider"))
.registry(registryConfig)
.service(service)
.protocol(protocolConfig)
.start()
.await();
}
}
注意,需要配置RegistryConfig自己的zookeeper, protocolConfig.setHost("xxx.xxx.xxx.xxx")設定成你本地內網的ip即可;
DemoServiceImpl類詳情——
public class DemoServiceImpl implements DemoService {
private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);
@Override
public String sayHello(String name) {
logger.info("Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
}
@Override
public CompletableFuture<String> sayHelloAsync(String name) {
return null;
}
}
接著,啟動消費者,這裡可以設定一個休眠時間,這樣就可以一直維持消費者執行在記憶體當中——
public class Application {
public static void main(String[] args) {
runWithRefer();
}
private static void runWithRefer() {
RegistryConfig registryConfig = new RegistryConfig("zookeeper://127.0.0.1:2181");
registryConfig.setTimeout(30000);
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setHost("192.168.200.1");
protocolConfig.setPort(20899);
ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
reference.setApplication(new ApplicationConfig("dubbo-demo-api-consumer"));
reference.setRegistry(registryConfig);
reference.setInterface(DemoService.class);
DemoService service = reference.get();
String message = service.sayHello("dubbo");
System.out.println("列印了5555555"+message);
try {
Thread.sleep(100000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
當Dubbo的服務提供者與消費者都正常執行時,說明此時JVM虛擬機器記憶體裡已經存在動態生成的代理類,這時,我們就可以開始通過arthas-boot.jar工具進行檢視了。
首先,將arthas-boot.jar工具下載到你本地,我的是Windows,隨便放到一個目錄當中,例如——
接著,直接在執行著Dubbo消費端程式的IDEA上開啟Terminal——
然後,輸入 java -jar C:\Users\92493\Downloads\12229238_g\arthas-boot.jar ,arthas正常執行成功話,將列出當前JVM上執行的程式——
可以看到我們剛剛啟動的provider程式與consumer程式,這時,只需要輸入對應程式前面的編號【5】,就可以將Arthas 關聯到啟動類為 org.apache.dubbo.demo.consumer.Application的 Java 程式上了——
到這一步,我們就可以通過指令 sc *.proxy *模糊查詢帶有proxy標誌的類名了,動態代理生成的類一般都是以Proxy標誌——
其中,這裡的org.apache.dubbo.common.bytecode.proxy0就是消費者生成的動態代理類,我們可以直接反編譯去檢視它內部結構——
[arthas@57676]$ jad org.apache.dubbo.common.bytecode.proxy0
控制檯就會列印出該動態代理類的內部結構——
/*
* Decompiled with CFR.
*
* Could not load the following classes:
* com.alibaba.dubbo.rpc.service.EchoService
* org.apache.dubbo.common.bytecode.ClassGenerator$DC
* org.apache.dubbo.demo.DemoService
* org.apache.dubbo.rpc.service.Destroyable
*/
package org.apache.dubbo.common.bytecode;
import com.alibaba.dubbo.rpc.service.EchoService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.concurrent.CompletableFuture;
import org.apache.dubbo.common.bytecode.ClassGenerator;
import org.apache.dubbo.demo.DemoService;
import org.apache.dubbo.rpc.service.Destroyable;
public class proxy0 implements ClassGenerator.DC,Destroyable,EchoService,DemoService {
public static Method[] methods;
private InvocationHandler handler;
public String sayHello(String string) {
Object[] objectArray = new Object[]{string};
Object object = this.handler.invoke(this, methods[0], objectArray);
return (String)object;
}
public CompletableFuture sayHelloAsync(String string) {
Object[] objectArray = new Object[]{string};
Object object = this.handler.invoke(this, methods[1], objectArray);
return (CompletableFuture)object;
}
public Object $echo(Object object) {
Object[] objectArray = new Object[]{object};
Object object2 = this.handler.invoke(this, methods[2], objectArray);
return object2;
}
public void $destroy() {
Object[] objectArray = new Object[]{};
Object object = this.handler.invoke(this, methods[3], objectArray);
}
public proxy0() {
}
public proxy0(InvocationHandler invocationHandler) {
this.handler = invocationHandler;
}
}
在Dubbo案例當中,當我們執行 String message = service.sayHello("dubbo")去呼叫遠端介面時,其實是呼叫了動態代理生成的方法——
public String sayHello(String string) {
Object[] objectArray = new Object[]{string};
Object object = this.handler.invoke(this, methods[0], objectArray);
return (String)object;
}
舉一反三,這個Arthas工具類可以線上上生產環境檢視一些我們新部署的程式碼,看是否是新改動的。