简单解析类文件(*.Class)的编译版本

来源:互联网 发布:华小夏汉服淘宝 编辑:程序博客网 时间:2024/05/18 03:05

一个简单的解析类文件(*.Class)的编译版本的小程序:

    public static void main(String args[]) throws Exception {        Map<Integer, String> vers = new HashMap<Integer, String>();        vers.put(49, "JDK_5");        vers.put(50, "JDK_6");        vers.put(51, "JDK_7");        vers.put(52, "JDK_8");        // 也可以使用 ASM 读取类的版本号,经测试ASM读取的版本与本程序结果一致。//        ASM 支持的 JDK 版本//        System.out.println(Opcodes.V1_5);//        System.out.println(Opcodes.V1_6);//        System.out.println(Opcodes.V1_7);//        System.out.println(Opcodes.V1_8);            // 读取文件数据,文件是当前目录下的First.class            FileInputStream fis = new FileInputStream("/test.class");            byte[] data = new byte[fis.available()];            // 读取文件到字节数组            fis.read(data);            fis.close();            // 解析主版本号和次版本号码//            int minor_version = (((int) data[4]) << 8) + data[5];            int major_version = (((int) data[6]) << 8) + data[7];            System.out.println("编译版本:" + vers.get(major_version));    }

欲更多了解Class文件,请继续...


每个合法的 Java 类文件都具备精确的定义,而正是这种精确的定义,才使得 Java 虚拟机得以正确读取和解释所有的 Java 类文件。

Java 类文件是 8 位字节的二进制流。数据项按顺序存储在 class 文件中,相邻的项之间没有间隔,这使得 class 文件变得紧凑,减少存储空间。在 Java 类文件中包含了许多大小不同的项,由于每一项的结构都有严格规定,这使得 class 文件能够从头到尾被顺利地解析。下面让我们来看一下 Java 类文件的内部结构,以便对此有个大致的认识。

例如,一个最简单的 Hello World 程序:

public class HelloWorld {  public static void main(String[] args) {  System.out.println("Hello world");  }  }

经过 javac 编译后,得到的类文件大致是:

从上图中可以看到,一个 Java 类文件大致可以归为 10 个项:

  • Magic:该项存放了一个 Java 类文件的魔数(magic number)和版本信息。一个 Java 类文件的前 4 个字节被称为它的魔数。每个正确的 Java 类文件都是以 0xCAFEBABE 开头的,这样保证了 Java 虚拟机能很轻松的分辨出 Java 文件和非 Java 文件。
  • Version:该项存放了 Java 类文件的版本信息,它对于一个 Java 文件具有重要的意义。因为 Java 技术一直在发展,所以类文件的格式也处在不断变化之中。类文件的版本信息让虚拟机知道如何去读取并处理该类文件。
  • Constant Pool:该项存放了类中各种文字字符串、类名、方法名和接口名称、final 变量以及对外部类的引用信息等常量。虚拟机必须为每一个被装载的类维护一个常量池,常量池中存储了相应类型所用到的所有类型、字段和方法的符号引用,因此它在 Java 的动态链接中起到了核心的作用。常量池的大小平均占到了整个类大小的 60% 左右。
  • Access_flag:该项指明了该文件中定义的是类还是接口(一个 class 文件中只能有一个类或接口),同时还指名了类或接口的访问标志,如 public,private, abstract 等信息。
  • This Class:指向表示该类全限定名称的字符串常量的指针。
  • Super Class:指向表示父类全限定名称的字符串常量的指针。
  • Interfaces:一个指针数组,存放了该类或父类实现的所有接口名称的字符串常量的指针。以上三项所指向的常量,特别是前两项,在我们用 ASM 从已有类派生新类时一般需要修改:将类名称改为子类名称;将父类改为派生前的类名称;如果有必要,增加新的实现接口。
  • Fields:该项对类或接口中声明的字段进行了细致的描述。需要注意的是,fields 列表中仅列出了本类或接口中的字段,并不包括从超类和父接口继承而来的字段。
  • Methods:该项对类或接口中声明的方法进行了细致的描述。例如方法的名称、参数和返回值类型等。需要注意的是,methods 列表里仅存放了本类或本接口中的方法,并不包括从超类和父接口继承而来的方法。使用 ASM 进行 AOP 编程,通常是通过调整 Method 中的指令来实现的。
  • Class attributes:该项存放了在该文件中类或接口所定义的属性的基本信息。

    使用 ASM ,开发者不需要熟知 class 文件的每一段,以及它们的功能、长度、偏移量以及编码方式,ASM 会给我们照顾好这一切。

    使用ASM5解析Class文件的编译版本:

import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.Opcodes;import java.io.*;import java.util.HashMap;import java.util.Map;public class ClassInfoPrinter extends ClassVisitor {    public ClassInfoPrinter() {        super(Opcodes.ASM5);    }    @Override    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {        Map<Integer, String> vers = new HashMap<Integer, String>();        vers.put(49, "JDK_5");        vers.put(50, "JDK_6");        vers.put(51, "JDK_7");        vers.put(52, "JDK_8");        System.out.println("编译版本:" + vers.get(version));    }    public static void main(String[] args) throws IOException {        ClassReader classReader = new ClassReader(new FileInputStream("/test.class"));        classReader.accept(new ClassInfoPrinter(), ClassReader.SKIP_DEBUG + ClassReader.SKIP_FRAMES + ClassReader.SKIP_CODE);    }}

详细:http://www.ibm.com/developerworks/cn/java/j-lo-asm30/index.html


0 0
原创粉丝点击