• Alibaba Java诊断工具Arthas查看Dubbo动态代理类


    原创/朱季谦

    阅读Dubbo源码过程中,会发现,Dubbo消费端在做远程调用时,默认通过 Javassist 框架为服务接口生成动态代理类,调用javassist框架下的JavassistProxyFactory类的getProxy(Invoker invoker, Class<?>[] interfaces)方法,动态生成一个存放在JVM中的动态代理类。

    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源码中的例子,分别启动了提供者与消费者。
    image

    首先,启动提供者方法——

    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,随便放到一个目录当中,例如——
    image

    接着,直接在运行着Dubbo消费端进程的IDEA上打开Terminal——

    image

    然后,输入 java -jar C:\Users\92493\Downloads\12229238_g\arthas-boot.jar ,arthas正常运行成功话,将列出当前JVM上运行的进程——

    image

    可以看到我们刚刚启动的provider进程与consumer进程,这时,只需要输入对应进程前面的编号【5】,就可以将Arthas 关联到启动类为 org.apache.dubbo.demo.consumer.Application的 Java 进程上了——

    image

    到这一步,我们就可以通过指令 sc *.proxy *模糊查询带有proxy标志的类名了,动态代理生成的类一般都是以Proxy标志——

    image

    其中,这里的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工具类可以在线上生产环境查看一些我们新部署的代码,看是否是新改动的。

  • 相关阅读:
    python学习笔记 class
    工作随谈之扯淡
    性能测试你了解多少?
    SVN常见问题汇总
    使用appium做自动化测试时,send_keyss只能输入字母数字,无法输入中文
    python+appium-desktop:安卓(android)7.0以上使用appium无法定位元素(无法refresh)且 无法运行脚本
    JMeter处理返回结果unicode转码为中文
    国内手机号段汇总(全)-2018-09
    Python : *args和**kwargs是什么东东呢?
    selenium 3.0变化
  • 原文地址:https://www.cnblogs.com/zhujiqian/p/16114954.html
Copyright © 2020-2023  润新知