学习动态代理前先看看-ASM

关键词:ASM    字节码

 


《学习动态代理前先看看-class字节码》中提到,我们是直接可以面向.class文件进行编码的,但是编写一个字节码内容是十分复杂的,好在有框架提供了功能。比如:ASM。ASM是一个通用的Java字节码操作和分析框架。它可用于修改现有的类或直接以二进制形式动态生成类。(关于ASM详细内容,请戳ASM官网

ASM提供了两种API用来生成或者修改已经编译好的类,core API 和 tree API。其中core API是基于事件的(event based),tree API是基于对象的(object based )。

在基于事件的模型中,一个类用一个事件序列所表示,序列中的一个事件表示类的一个元素,比如头信息,一个属性、一个方法声明以及指令等。在基于对象的模型中,一个类用一个对象树表示,每个对象表示类的一部分,比如类本身,一个属性,一个方法等。比较两种模型来说,基于事件的模型速度更快,占用内存更少,但是使用起来比较困难。而基于对象的模型使用起来相对简单一些。


core API

主要是三个类:ClassVisitor、ClassReader、ClassWriter。

ClassVisitor:抽象类,ClassVisitor类将它接收到的所有方法调用委托给另一个ClassVisitor实例。它可以被看作是一个事件过滤器。

ClassReader:负责解析一个已经编译好的类。可以看做是一个事件生成者。其中的accept方法接收一个ClassVisitor,用于调用ClassVisitor的方法。

ClassWriter:继承了ClassVisitor,可以直接以二进制形式构建已编译的类。可以看做是一个事件消费者。通过方法toByteArray将编译好的类输出为字节数组。

ClassReader结合ClassVisitor的使用:

①引入Maven依赖

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>9.0</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-util</artifactId>
    <version>9.0</version>
</dependency>

②编写一个类继承ClassVisitor,并重写所有方法:

import org.objectweb.asm.*;

public class MyClassVisitor extends ClassVisitor {

    public MyClassVisitor() {
        super(589824);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        System.out.println("visit version="+version+" access="+access+" name="+name+" signature="+signature+" superName"+superName+" interfaces="+interfaces);
    }

    @Override
    public void visitSource(String source, String debug) {
        System.out.println("visitSource source="+source+" debug="+debug);
    }

    @Override
    public ModuleVisitor visitModule(String name, int access, String version) {
        System.out.println("visitModule name="+name+" access="+access+" version="+version);
        return null;
    }

    @Override
    public void visitNestHost(String nestHost) {
        System.out.println("visitNestHost nestHost="+nestHost);
    }

    @Override
    public void visitOuterClass(String owner, String name, String descriptor) {
        System.out.println("visitOuterClass owner="+owner+" name="+name+" descriptor="+descriptor);
    }

    @Override
    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
        System.out.println("visitAnnotation descriptor="+descriptor+" visible="+visible);
        return null;
    }

    @Override
    public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {
        System.out.println("visitTypeAnnotation typeRef="+typeRef+" descriptor="+descriptor+" visible="+visible);
        return null;
    }

    @Override
    public void visitAttribute(Attribute attribute) {
        System.out.println("visitAttribute "+attribute.type);
    }

    @Override
    public void visitNestMember(String nestMember) {
        System.out.println("visitNestMember "+nestMember);
    }

    @Override
    public void visitPermittedSubclass(String permittedSubclass) {
        System.out.println("visitPermittedSubclass "+permittedSubclass);
    }

    @Override
    public void visitInnerClass(String name, String outerName, String innerName, int access) {
        System.out.println("visitInnerClass name="+name+" outerName="+outerName+" innerName="+innerName+" access="+access);
    }

    @Override
    public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {
        System.out.println("visitRecordComponent name="+name+" descriptor="+descriptor+" signature="+signature);
        return null;
    }

    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        System.out.println("visitField access="+access+" name="+name+" descriptor="+descriptor+" signature="+signature+" value="+value);
        return null;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        System.out.println("visitMethod access="+access+" name="+name+" descriptor="+descriptor+" signature="+signature+" exceptions="+exceptions);
        return null;
    }

    @Override
    public void visitEnd() {
        System.out.println("visitEnd");
    }
}

③编写两个类,Dog和Animal

package read;

public class Dog extends Animal{

    private String name;

    public void say() {
        System.out.println("a dog");
    }

    public String getName() {
        return name;
    }

    private void setName() {
        name = "大黄";
    }

}
package read;

public class Animal {
}

④运行代码

public static void main(String[] args) {

    try {
        MyClassVisitor myClassVisitor = new MyClassVisitor();
        // 此处传参ArrayList的全限定名,构造方法内部会调用ClassLoader.getSystemResourceAsStream加载类信息
        ClassReader classReader = new ClassReader("read.Dog");
        classReader.accept(myClassVisitor, 0);
    } catch (Exception e) {
        e.printStackTrace();
    }

}

⑤查看控制台输出结果

visit version=52 access=33 name=read/Dog signature=null superNameread/Animal interfaces=[Ljava.lang.String;@61baa894
visitSource source=Dog.java debug=null
visitField access=2 name=name descriptor=Ljava/lang/String; signature=null value=null
visitMethod access=1 name=<init> descriptor=()V signature=null exceptions=null
visitMethod access=1 name=say descriptor=()V signature=null exceptions=null
visitMethod access=1 name=getName descriptor=()Ljava/lang/String; signature=null exceptions=null
visitMethod access=2 name=setName descriptor=()V signature=null exceptions=null
visitEnd

从输出的内容可以发现,字节码也是有语法的,在java源码的语法与字节码的语法有一套对应的关系。比如:void 对应 ()V,Ljava/lang/String;对应String。

ClassWriter结合ClassVisitor的使用:

①通过ClassWriter生成一个上面的Dog类。可结合上面的控制台输出的内容传入相关参数:

public static void main(String[] args) throws Exception{
    ClassWriter classWriter = new ClassWriter(0);
    // 写 public class read.Dog extends read.Animal
    classWriter.visit(52,33, "read/Dog", null, "read/Animal", null);
    // 写属性 name
    classWriter.visitField(2, "name", "Ljava/lang/String", null, null);
    // 依次写方法
    // 构造方法
    classWriter.visitMethod(1, "<init>", "()V", null ,null);
    // say方法
    classWriter.visitMethod(1, "say", "()V", null ,null);
    // getName方法
    classWriter.visitMethod(1, "getName", "()Ljava/lang/String;", null ,null);
    // setName方法
    classWriter.visitMethod(2, "setName", "()V", null ,null);
    classWriter.visitEnd();
    // 获取字节码
    byte[] bytes = classWriter.toByteArray();
    // 将内容写到本地文件中
    FileOutputStream fos = new FileOutputStream("D:\\Dog.class");
    fos.write(bytes);

}

②用IDEA打开Dog.class

package read;

import Ljava.lang.String;

public class Dog extends Animal {
    private String name;

    public Dog() {
    }

    public void say() {
    }

    public java.lang.String getName() {
    }

    private void setName() {
    }
}

Dog类的内容写的有问题,由于没有深入学习core api的使用,暂时只能到这个程度了。从使用的过程发现,确实难用。


█ tree API

tree API的核心是ClassNode,对应Class。ClassNode继承了ClassVisitor,可以使用ClassVisitor中的方法和功能。有ClassNode,自然也会有FieldNode对应类中的属性,MethodNode对应类中的方法等等。使用tree API编程更有面向对象的感觉。

 


版权声明:本文为weixin_50518271原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。