ASM字节码处理框架是用Java开发的而且使用基于访问者模式生成字节码及驱动类到字节码的转换,通俗的讲,它就是对class文件的CRUD,经过CRUD后的字节码可以转换为类。ASM的解析方式类似于SAX解析XML文件,它综合运用了访问者模式、职责链模式、桥接模式等多种设计模式,相对于其他类似工具如BCEL、SERP、Javassist、CGLIB,它的最大的优势就在于其性能更高,其jar包仅30K。Hibernate和Spring都使用了cglib代理,而cglib本身就是使用的ASM,可见ASM在各种开源框架都有广泛的应用。
ASM是一个强大的框架,利用它我们可以做到:
1、获得class文件的详细信息,包括类名、父类名、接口、成员名、方法名、方法参数名、局部变量名、元数据等
2、对class文件进行动态修改,如增加、删除、修改类方法、在某个方法中添加指令等
下面以一个具体实例来演示ASM的强大功能,实现的功能很简单:有一个已经存在的class文件,需要在运行时生成一个该类的子类,并在该类的某个方法中添加几条指令,具体步骤如下:
第一步:定义class文件访问器
public class AsmClassVisit extends ClassAdapter {
private String className; //用于保存原始类名
public AsmClassVisit(ClassVisitor cv) {
super(cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
className = name;
superName = name;
name = name + AOPEngine.CHILD_POSTFIX; //定义子类的名字
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);//先得到原始的方法
MethodVisitor newMethod = null;
if("add".equals(name)) //此处的add即为需要修改的方法
{
newMethod = new AsmMethodVisit(mv); //访问需要修改的方法
return newMethod;
}
if("<init>".equals(name)) //还需要改造子类的构造方法
{
newMethod = new ChangeConstructorMethodAdapter(mv, className);
return newMethod;
}
return mv;
}
}
第二步:定义方法访问器:
public class AsmMethodVisit extends MethodAdapter {
public AsmMethodVisit(MethodVisitor mv, String advisorClassName, List<Advisor> advisors) {
super(mv);
this.advisorClassName = advisorClassName;
this.advisors = advisors;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
super.visitMethodInsn(opcode, owner, name, desc);
}
@Override
public void visitCode() {
//此方法在访问方法的头部时被访问到,仅被访问一次
//此处可插入新的指令
super.visitCode();
}
@Override
public void visitInsn(int opcode) {
//此方法可以获取方法中每一条指令的操作类型,被访问多次
//如应在方法结尾处添加新指令,则应判断:
if(opcode == Opcodes.RETURN)
{
mv.visitTypeInsn(Opcodes.NEW, "com/xyz/Check");//新建一个Check类
mv.visitInsn(Opcodes.DUP);//NEW指令完后,对象的引用将从栈中弹出,而INVOKESPECIAL需要该引用,用DUP指令增加对象的引用
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "com/xyz/Check", "<init>", "()V");//调用构造方法,初始化Check类
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/xyz/Check", "checkSecurity", "()V");//调用类中的方法
}
super.visitInsn(opcode);
}
}
第三步:修改子类的构造方法
public class ChangeConstructorMethodAdapter extends MethodAdapter {
private String superClassName;//用于接收父类名
public ChangeConstructorMethodAdapter(MethodVisitor arg0, String superClassName) {
super(arg0);
this.superClassName = superClassName;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
//如果当前指令是调用父类的构造方法
if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) {
owner = superClassName;
}
super.visitMethodInsn(opcode, owner, name, desc);//改写父类为superClassName
}
}
第四步:读取并动态生成子类
ClassReader cr = new ClassReader(className);//className为原始类名,形如com.xyz.Abc,但在web容器环境下不可这样写
ClassWriter cw = new ClassWriter(false);
AsmClassVisit mv = new AsmClassVisit(cw);
cr.accept(mv, true);
MyspringClassLoader mcl = new MyspringClassLoader(this.getClass().getClassLoader());//定义自己的classLoader,用于根据byte数组load类
Class newClass = mcl.getClass(cw.toByteArray(), null);//生成子类
看得出来,如果需要熟悉运用ASM,需要具备一定的虚拟机指令集等相关知识,而ASM为我们提供了一些辅助工具,可以帮助我们避开直接写指令,如ASMifierClassVisitor和ASMifierCodeVisitor类,这两个类可以将对某个方法的调用进行转换,可以直接得到方法的虚拟机指令。
以上只是ASM的一个简单应用,它所具备的功能远不止这些,灵活使用它可以帮助我们完成看似难以完成的任务。