作者:董子龙前言前段时间一直想参考lombok的实现原理写一个可以生成业务文档的插件的专利和修改记录,然后查阅资料,无意中了解到了字节码增强工具——byteBuddy。但是由于当时时间紧迫,没有对组件进行深入的了解。其实在我们日常的开发中,字节码增强组件无处不在,比如spring-aop、mybatis。本着知其所以然的精神,我决定静下心来,对字节码增强技术做一个深入的学习和总结。这篇文章是本系列的开篇,主要是对字节码做一个简单的介绍,为我们后面的深入学习打下良好的基础。一、字节码简介字节码是一种中间状态的二进制文件,由源代码编译而成,可读性不如源代码高。CPU不能直接读取字节码。在Java中,字节码需要由JVM翻译成机械代码,然后CPU才能读取和运行它。使用字节码的优点:一次编译,到处运行。Java是典型的使用字节码作为中间语言。源码在一处编译,一个.class文件即可在各种电脑上运行。2、字节码增强的使用场景如果我们不想修改源代码,而是想增加新的功能让程序按照我们的预期运行,我们可以通过编译过程和加载过程做相应的操作。简单来说:将生成的.class文件修改或替换为我们需要的目标.class文件。由于字节码增强可以在完全不侵入业务代码的情况下嵌入代码逻辑,因此可以用来做一些很酷的事情,比如下面常见的场景:1.动态代理2.热部署3.调用链跟踪埋点4.动态插入log(性能监控)5.测试代码覆盖率跟踪...3.字节码增强实现方法字节码工具类创建与实现接口方法调用类扩展父类方法调用优点缺点常用学习成本java-proxy支持支持不支持支持不支持简单动态代理首选功能有限,不支持扩展spring-aop,MyBatis1starasmsupportsupportsupportsupportssupportsarbitrarybytecodeinsertion,learningdifficulty,writingcodemulti-cglib5starjavaassitsupportsupportsupportsupportsupport支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持支持bytebuddy不支持jdk1.5以上的语法看来EasyMock快被bytebuddy淘汰了,jackson-databind3-starbytebuddy支持支持支持支持支持支持支持支持任意维度的拦截,可以获取原始类、方法、代理类和所有参数。不直观,学习和理解成本很高,而且有很多APISkyWalking,Mockito,Hibernate,powermock3star四,简单的例子AOP是我们日常开发中常见的架构设计思想,AOP的主要实现有cglib,Aspectj,Javassist、java代理等。下面我们就从日常开发中遇到的方法执行前后打印日志开始,手动使用字节码实现AOP。定义目标接口并实现publicclassSayService{publicvoidsay(Stringstr){System.out.println("hello"+str);}}定义类SayService,在执行say方法前,打印方法并开始执行start,方法执行后打印方法执行结束endASM实现AOP4.1.1并引入jar包org.ow2.asmasm9.14.1.2、AOP具体实现publicclassResourceClassVisitorextendsClassVisitorimplementsOpcodes{publicResourceClassVisitor(ClassVisitorcv){super(Opcodes.ASM4,cv);}publicResourceClassVisitor(inti,ClassVisitorclassVisitor){super(i,classVisitor);}/**访问类基本信息*/@Overridepublicvoidvisit(intversion,intaccess,Stringname,Stringsignature,StringsuperName,String[]interfaces){this.cv.visit(version,access,name,signature,超名,接口);}/**访问方法的基本信息*/@OverridepublicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,Stringsignature,String[]exceptions){MethodVisitormv=this.cv.visitMethod(access,name,desc,signature,exceptions);//如果不是构造方法,我们构建MethodVisitor(MethodVisitor)if(!name.equals("")&&mv!=null){mv=newResourceClassVisitor.MyMethodVisitor((MethodVisitor)mv);}返回(MethodVisitor)mv;}/**自定义方法访问对象*/classMyMethodVisitorextendsMethodVisitorimplementsOpcodes{publicMyMethodVisitor(MethodVisitormv){super(Opcodes.ASM4,mv);}/**这个方法会在方法执行前执行*/@OverridepublicvoidvisitCode(){super.visitCode();this.mv.visitFieldInsn(Opcodes.GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");this.mv.visitLdcInsn(&qu哦;方法开始执行start");this.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V",false);}/**对应到方法体本身*/@OverridepublicvoidvisitInsn(intopcode){//在方法返回或异常之前,添加一个结束输出if((opcode>=Opcodes.IRETURN&&opcode<=Opcodes.RETURN)||opcode==Opcodes.ATHROW){this.mv.visitFieldInsn(Opcodes.GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");this.mv.visitLdcInsn("方法执行结束");this.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V",false);}this.mv.visitInsn(opcode);}}}publicclassAopTest{publicstaticvoidmain(String[]args)throwsIOException{//第一步:构建ClassReader对象,读取指定位置的class文件(默认为classpath-classpath)ClassReaderclassReader=newClassReader("com/aop/SayService");//Step2:构建一个ClassWriter对象,并基于这个对象创建一个新的class文件//ClassWriter.COMPUTE_FRAMES表示ASM会自动计算maxstacks,maxlocals和stackmapframe的具体内容//ClassWriter.COMPUTE_MAXS表示ASM会自动计算maxstacks和maxlocals,但不会自动计算stackmapframe。ClassWriterclassWriter=newClassWriter(ClassWriter.COMPUTE_FRAMES);//推荐使用COMPUTE_FRAMES//第三步:构建ClassVisitor对象,用于接收ClassReader对象的数据,并将数据传递给ClassWriter对象ClassVisitorclassVisitor=newResourceClassVisitor(类作家);//第四步:基于ClassReader读取类信息,并将数据传递给ClassVisitor对象//这里的参数ClassReader.SKIP_DEBUG表示跳过一些调试信息等,ASM代码看起来会更简洁//该参数这里的ClassReader.SKIP_FRAMES是指在某些方法中跳过部分栈帧信息。stackframe的手工计算很复杂,让系统来做。//推荐使用这两个参数classReader.accept(classVisitor,ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);//第五步:从ClassWriter中获取数据并将数据写入一个class文件byte[]data=classWriter.toByteArray();//将字节码写入磁盘classFileFilef=newFile("target/classes/com/aop/SayService.class");FileOutputStreamfout=newFileOutputStream(f);fout.write(数据);fout.close();SayServicers=newSayService();rs.say("asm");//开始,句柄(),结束}}4.1.3、测试类输出结果Javassist实现AOP4.2.1,导入jar包org.javassistjavassist3.28.0-GA4.2.2.AOP实现publicclassAopTest{publicstaticvoidmain(String[]args)throwsException{ClassPoolpool=ClassPool.getDefault();CtClasscc=pool.get("com.aop.SayService");CtMethodpersonFly=cc.getDeclaredMethod("say");personFly.insertBefore("System.out.println("方法执行开始");");personFly.insertAfter("System.out.println("方法执行结束");");cc.toClass();SayServicesayService=newSayService();sayService.say("协助");}}4.2.3、测试类输出结果5.总结作为字节码增强系列的开篇,只是简单介绍了字节码的定义和字节码的实现,最后通过具体的例子向大家展示了如何增强字节码。在后续的文章中,将讨论相关框架的原理和具体应用。做一个详细的总结,欢迎大家批评指正。