转自【阿里云云栖号】
一、前言
写这篇文章的时候我在想可能大部分程序员包括你我,常常都在忙于业务开发或奔波在日常维护与修复BUG的路上,当不能从中吸取技术营养与改变现状后,就像一台恒定运行的机器,逃不出限定宇宙速度的一个圈里。可能你也会有自己的难处,平时加班太晚没有时间学习、周末家里琐事太多没有精力投入,放假计划太满没有空闲安排。总之,学习就会被搁置。而当一年年的过去后,当自己的年龄与能力不成匹配后又会后悔没有给多投入一些时间学习成长。
尤其是一线编码的技术人,除了我们所能看到的在技术框架里(SSM)开发的业务代码,你是否有遇到过学习瓶颈,而这种瓶颈又是你自己不知道自己不会什么,就像下面这些技术列表里,你有了解多少;
- javaagent
- asm
- jvmti
- javaassit
- netty
- 算法,搜索引擎
- cglib
- 混沌工程
- 中间件开发
- 高级测试;压力测试、链路测试、流量回放、流量染色
- 故障系列;突袭、重现、演练
- 分布式的数据一致性
- 文件操作;es、hive
- 注册中心;zookeeper、Eureka
- 互联网工程开发技术栈;spring、mybaits、网关、rpc(thrift, grpc, dubbo)、mq、缓存redis、分库分表、定时任务、分布式事物、限流、熔断、降级
- 数据库binlog解析
- 架构设计;DDD领域驱动设计、微服务、服务治理
- 容器;k8s, docker
- 分布式存储;ceph
- 服务istio
- 压测 jmter
- Jenkins-部署java代码项目 + ansible
- 全链路监控,分布式追踪
- 语音识别、语音合成
- lvs nginx haproxy iptables
- hadoop mapreduce hive sqoop hbase flink kylin druid
好,现在开始就搞一下其中的一个技术点 ASM,看看它的真面目。那么学习之前先看下他有什么用途;
1.类的代理,如cglib
2.混沌工程
3.反向工程
4.结合 javaagent 做到非入侵式监控,方法耗时、日志、机器性能等等
5.破解
为了更方便的学习ASM,我将《ASM4使用手册》以及一些技术点整理成在线文档,可以随时方便查阅(asm.itstack.org);
二、环境配置
1.jdk 1.8
2.idea 2019.3.1
3.asm-commons 6.2.1
三、工程信息
- itstack-demo-asm-01:字节码编程,HelloWorld
- itstack-demo-asm-02:字节码编程,两数之和
- itstack-demo-asm-03:字节码增强,输出入参
- itstack-demo-asm-04:字节码增强,调用外部方法
四、HelloWorld还可以这样写
你所熟悉的HelloWorld是不这样;
那你有尝试反解析下他的类查看下汇编指令吗,javap -c HelloWorld
如果你还感兴趣其他指令,可以参考这个字节码指令表:Go!
好! 以上呢,是我很熟悉的一段代码了,那么现在我们把这段代码用ASM方式写出来;
import org.objectweb.asm.ClassWriter;import org.objectweb.asm.MethodVisitor;import org.objectweb.asm.Opcodes;
private static byte[] generate() {
ClassWriter classWriter = new ClassWriter(0);// 定义对象头;版本号、修饰符、全类名、签名、父类、实现的接口classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, "org/itstack/demo/asm/AsmHelloWorld", null, "java/lang/Object", null);// 添加方法;修饰符、方法名、描述符、签名、异常MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);// 执行指令;获取静态属性methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");// 加载常量 load constantmethodVisitor.visitLdcInsn("Hello World");// 调用方法methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);// 返回methodVisitor.visitInsn(Opcodes.RETURN);// 设置操作数栈的深度和局部变量的大小methodVisitor.visitMaxs(2, 1);// 方法结束methodVisitor.visitEnd();// 类完成classWriter.visitEnd();// 生成字节数组return classWriter.toByteArray();
}
以上的代码,“小朋友,你是否有很多问好???^1024”,其实以上的代码都是来自于 ASM 框架的代码,这里面所有的操作与我们使用使用 javap -c XXX 所反解析出的字节码是一样的,只不过是反过来使用指令来编写代码。
1.定义一个类的生成 ClassWriter
2.设定版本、修饰符、全类名、签名、父类、实现的接口,其实也就是那句;public class HelloWorld
3.接下来开始创建方法,方法同样需要设定;修饰符、方法名、描述符等。这里面有几个固定标识;
4.执行指令;获取静态属性。主要是获得 System.out
5.加载常量 load constant,输出我们的HelloWorld methodVisitor.visitLdcInsn("Hello World");
6.最后是调用输出方法并设置空返回,同时在结尾要设置操作数栈的深度和局部变量的大小
这样输出一个 HelloWorld 是不还是蛮有意思的,虽然你可能觉得这编码起来实在太难了吧,也非常难理解。首先如果你看过我的专栏,用《Java写一个Jvm虚拟机》,那么你可能会感受到这里面的知识点还是不那么陌生的。另外这里的编写,ASM还提供了插件,可以方便的让你开发字节码。接下来就介绍一下使用方式。
五、有插件的帮助字节码开发也不是很难
对于新人来说如果用字节码增强开发一些东西确实挺难,尤其是一些复杂的代码块使用字节码指令操作还是很有难度的。那么,其实也是有简单办法就是使用 ASM 插件。这个插件可以很轻松的让你看到一段代码的指令码以及如何用ASM去开发。
1.安装插件(ASM Bytecode Outline)
2.测试使用
是不是看到有插件的帮助下,心里有所激动了,至少写这样的东西有了抓手。这样你就可以很方便的去操作一些增强字节码的功能了。
六、用字节码写出一个两数之和计算
好!有了上面的插件,也有了一些基础知识的了解。那么我们开发一个计算两数之和的方法,之后运行计算结果。
这是我们的目标
使用字节码编程方式实现
- 上面有两个括号 {},第一个是用于生成一个空的构造函数
- 接下来的指令就比较简单了,首先使用 ILOAD 进行数值的两次压栈也就是弄到操作数栈里去操作,接下来开始执行 IADD,将两数相加。
- 最后返回结果 IRETURN ,注意是返回的 I 类型。到此这段方法快就实现完成了。反编译后如下;
这段执行操作和我们在使用 java 的反射操作一样,也是比较容易的。此时我们是调用了新的字节码类,同时还将字节码输出方便我们查看生成的 class 类。
七、在原有方法上字节码增强监控耗时
到这我们基本了解到通过字节码编程,可以动态的生成一个类。但是在实际使用的过程中,我们可能有的时候是需要修改一个原有的方法,在开始和结尾添加一些代码,来监控这个方法的耗时。这也是非侵入式监控的最基本模型。
整体的代码块有点大,我们可以分为块来看,如下;
ClassReader cr = new ClassReader(MyMethod.class.getName()); 读取原有类,也是字节码增强的开始ClassVisitor cv = new ProfilingClassAdapter(cw, MyMethod.class.getSimpleName()); 开始增强字节码onMethodEnter,onMethodExit,在方法进入和方法退出时添加耗时执行的代码。
测试结果:
直接运行TestMonitor.java;
八、字节码控制打印方法的入参
那么除了可以监控方法的执行耗时,还可以将方法的入参信息进行打印出来。这样就可以在一些异常情况下,看到日志信息。
其他代码与上面相同,这里只列一下修改的地方
从这里可以看到,在方法进入时候使用指令码 GETSTATIC,获取输出对象类
- 接下来使用 ALOAD,从局部变量1中装载引用类型值入栈
- 最后输出入参信息
- 测试结果:
直接运行TestMonitor.java;
九、用字节码增强调用外部方法好!那么执行到这,我们可以想到如果只是将一些信息打印到控制台还是没有办法做业务的,我们需要在这个时候将各种属性信息调用外部的类,进行发送到服务端。比如使用;mq、日志等。
定义日志信息输出类
十、总结
- 高级编程技术的内容还不止于此,不要只为了一时的功能实现,而放弃深挖深究的机会。也许就是你不断的增强拓展个人的知识技能,才让你越来越与众不同。
- ASM 这种字节码编程的应用是非常广的,但可能确实平时看不到的,因为他都是与其他框架结合一起作为支撑服务使用。像这样的技术还有很多,比如 javaassit、netty等等。
- 对于真的要学习一样技术时,不要只看爽文,但爽文也确实给了你敲门砖。当你要彻底的掌握某个知识的时候,最重要的是成体系的学习!压榨自己的时间,做有意义的事,是3-7年开发人员最正确的事