我们已经了解了java字节码基本的知识,我们应该能对java运行的一些原理,以及优化有了一个基本的认识。
但是,如果这个时候,直接手写字节码,还是很痛苦的。好在有些类库已经为我们写好了,我们直接拿来用,能省好多事情。
正所谓,站在巨人的肩膀!!!
选择哪一个字节码操作库?
大家熟知的字节码例如javasist ASM 等等。其中,ASM用的算是比较广泛。因为它小巧,效率高!
ASM 内部的逻辑是使用的是访问者模式的。具体可以参考我的另一篇文章:http://www.jianshu.com/p/765dfe6c7e02
访问者模式的好处是:分离算法的实现和算法操作结构之间的耦合,这样 就能在不改变被操作的数据结构的情况下,增加对数据的操作方法。
ASM
在ASM中,accpter包括:ClassReader class and the MethodNode class
他们的方法原型是
void accept(ClassVisitor cv)
void accept(MethodVisitor mv)
Visitor 接口包括 ClassVisitor, AnnotationVisitor, FieldVisitor, 和 MethodVisitor.这些visit方法的签名大致如下
void visit(int version, int access, String name, String signature, String superName, String[] interfaces)AnnotationVisitor visitAnnotation(String desc, boolean visible)
void visitAttribute(Attribute attr)
void visitEnd()
FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
void visitInnerClass(String name, String outerName, String innerName, int access)
MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
void visitOuterClass(String owner, String name, String desc)
void visitSource(String source, String debug)
时序图
基本上时序图就是在不停的访问类中的区块
初识ASM
我们通过一段代码来简单的认识下ASM
下面的代码的作用是,复制一份class文件
public static void main(String args []){
String classFilePath ="F:\Main.class";
String classFilePathDest ="F:\MainCopy.class";FileInputStream fis = null; try { fis = new FileInputStream(classFilePath); ClassReader cr = new ClassReader(fis); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); cr.accept(cw,0); FileOutputStream fos = new FileOutputStream(classFilePathDest); fos.write(cw.toByteArray());; fos.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
分析,通过fileInputstream读取一个class文件流之后,使ClassReader去读取这个字节流。
ClassWriter是继承自ClassVisitor的 ,ClassWriter.COMPUTE_FRAMES 代表让asm计算堆栈帧。
cr.accept(cw,0);驱使cw去不断的产生一系列的visit事件,达到复制一份相同的class文件的目的
小例子,在每一个方法的执行之前和执行之后,都插入一段代码
这里定义一个改写method的适配器,继承自ClassVisitor,只需要覆盖掉visitMethod方法即可。我们在这个方法中拦截,这个方法有一个返回值叫methodVisitor,然后定义一个methodvisitor类直接去执行改写逻辑即可。
其中
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
mv.visitLdcInsn(" before method exec , method in "+ owner +" ,name="+name);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
可以通过字节码反编译工具查看的!
完整的adapter代码如下
package com.zxy.test.javaagent.hello.asm.wrapmethod;import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;/**
-
Created by zxy on 2017/3/30.
*/
public class WrapMethodClassAdapter extends ClassVisitor implements Opcodes {public WrapMethodClassAdapter(final ClassVisitor cv) {
super(ASM5, cv);
}@Override
public MethodVisitor visitMethod(int i, String s, String s1, String s2, String[] strings) {
MethodVisitor mv = super.visitMethod(i, s, s1, s2, strings);
return ( mv == null ) ? null :new WrapMethodVisitor(mv);
}public static class WrapMethodVisitor extends MethodVisitor {
public WrapMethodVisitor( MethodVisitor mv) { super(ASM5,mv); } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { //方法执行之前打印 mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;"); mv.visitLdcInsn(" before method exec , method in "+ owner +" ,name="+name); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); mv.visitMethodInsn(opcode, owner, name, desc, itf); //方法执行之后打印 mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;"); mv.visitLdcInsn(" after method exec , method in "+ owner +" ,name="+name); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); }
}
}
调用代码
感觉下,和之前代码有啥区别啊
public static void main(String args []){
String classFilePath ="F:\Main.class";
String classFilePathDest ="F:\MainCopy.class";
FileInputStream fis = null;
try {
fis = new FileInputStream(classFilePath);
ClassReader cr = new ClassReader(fis);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
WrapMethodClassAdapter wrapMethodClassAdapter = new WrapMethodClassAdapter(cw);
//cr.accept(cw,0);
cr.accept(wrapMethodClassAdapter,0);
FileOutputStream fos = new FileOutputStream(classFilePathDest);
fos.write(cw.toByteArray());;
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
运行结果
参考:
http://download.forge.objectweb.org/asm/asm4-guide.pdf
http://asm.ow2.org/asm50/javadoc/user/index.html
http://asm.ow2.org/