Java深度理解——Java字节代码的操纵

  导读:Java作为业界应用最为广泛的言语之一, 毕竟是很少会有人涉及的话题。 InfoQ中文站特地约请IBM初级工程师成富为大家撰写这个《Java深度历险》专栏, 开发人员使用Java的方式比较复杂。 打开惯用的IDE, 再应用IDE提供的功能直接运行Java顺序就可以了。 这种开发形式面前的进程是:开发人员编写的是Java源代码文件(. java), IDE会负责调用Java的编译器把Java源代码编译成平台无关的字节代码(bytecode), 以类文件的形式保存在磁盘上(. class)。 Java经过这种方式来完成其“编写一次, runanywhere)”的目的。 Java类文件中包括的字节代码可以被不同平台上的JVM所使用。 也可以经过网络方式来下载, 还可以只存在于内存中。 JVM中的类加载器会负责从包括字节代码的字节数组(byte[])中定义出Java类。 可能会需要静态的生成Java字节代码, 或是对已有的Java字节代码停止修改。 首先介绍一下如何静态编译Java源文件。   静态编译Java源文件  在普通状况下, 对有些应用来说, 这个时候就需要静态编译源代码来生成Java字节代码, 再由JVM来加载执行。 典型的场景是很多算法竞赛的在线评测系统(如PKUJudgeOnline), 由系统在后台编译、运行并停止判定。   JSR199引入了Java编译器API。 如果使用JDK6的话, 可以经过此API来静态编译Java代码。 比方下面的代码用来静态编译最复杂的HelloWorld类。 该Java类的代码是保存在一个字符串中的。   如果不能使用JDK6提供的Java编译器API的话, 可以使用JDK中的工具类com. sun. tools. Main, 不过该工具类只能编译寄存在磁盘上的文件, 这是EclipseJava开发环境使用的增量式Java编译器, 该编译器也可以独自使用。 Play框架在外部使用了JDT的编译器来静态编译Java源代码。 在开发形式下, Play框架会活期扫描项目中的Java源代码文件, 一旦发现有修改, 使用这些静态编译的方式的时候, jar在应用的CLASSPATH中。   下面介绍一个例子, 比方求出来(3+4)7-10的值。 思索到括号的存在和运算符的优先级等成绩, 另外一种做法是可以用JSR223引入的脚本言语支持, 直接把输出的表达式当做JavaScript或是JavaFX脚原本执行, 接着加载Java类来执行并获取结果。 这种做法完全使用Java来完成。   //省略静态编译Java源代码的相关代码, 参见上一节  下面的代码给出了使用静态生成的Java字节代码的基本形式, 创建Java类的对象的实例, 再经过Java反射API来调用对象中的方法。   Java字节代码增强  Java字节代码增强指的是在Java字节代码生成之后, 对其停止修改, 增强其功能。 在很多Java框架中都可以见到这种完成方式。 Java字节代码增强通常与Java源文件中的注解(annotation)一块使用。 由框架在运行时辰完成对字节代码的增强。 Java字节代码增强应用的场景比较多, 普通都集中在减少冗余代码和对开发人员屏蔽底层的完成细节上。 用过JavaBeans的人可能对其中那些必须添加的getter/setter方法感到很繁琐, 并且难以维护。 而经过字节代码增强, 用过JPA的人, 会发理想体类中被添加了一些额外的域和方法。 字节代码增强在面向方面编程(AOP)的一些完成中也有使用。   在讨论如何停止字节代码增强之前, 首先介绍一下表示一个Java类或接口的字节代码的组织形式。   类文件{  0xCAFEBABE, 大版本号, A砍厥椋?当前类信息, 完成的接口信息数组, 域个数,   域信息数组, 方法个数, 方法信息数组, 属性个数, 属性信息数组  如上所示, 一个类或接口的字节代码使用的是一种松懈的组织构造, 其中所包括的内容顺次陈列。 如所完成的接口、域、方法和属性等, 有其不同的外部构造。 而且容易出错。 使用这些类库可以在一定水平上降低增强字节代码的复杂度。 比方思索下面一个复杂的需求, 熟悉AOP的人都知道, 可以用一个前增强(beforeadvice)来处理这个成绩。 上述示例中, 增强部分的逻辑比较复杂, out. println方法的调用。 在字节代码中, Java方法体是由一系列的指令组成的。 out. println方法的指令, 并把这些指令拔出到指令集合的最前面。 不过熟悉全部的指令比较困难。 ASM提供了一个工具类ASMifierClassVisitor, 可以打印出Java类的字节代码的构造信息。 当需要增强某个类的时候, 可以先在源代码上做出修改, 再经过此工具类来比较修改前后的字节代码的差异, 从而确定该如何编写增强的代码。 在JVM执行之前。 当获取到Java类的字节代码之后, 先停止增强处理, 再从修改正的字节代码中定义出Java类。 lang. 基本的思路是在JVM启动的时候添加一些代理(agent)。 每个代理是一个jar包, 这个类会包括一个premain方法。 JVM在启动的时候会首先执行代理类的premain方法, 在premain方法中就可以对顺序自身的字节代码停止修改。 JDK6中还允许在JVM启动之后静态添加代理。 instrument包支持两种修改的场景, 一种是重定义一个Java类, 即完全交流一个Java类的字节代码;另外一种是转换已有的Java类, 相当于前面提到的类字节代码增强。 还是以前面提到的输出方法执行日志的场景为例, 首先需要完成java. lang. instrument. 就可以在代理的premain方法中使用它。 运行Java顺序的时候, 添加JVM启动参数-javaagent:myagent. 这样的话, 完成相关的转换操作。 非常适合于功能剖析、调试跟踪和日志记载等任务。 开发人员应该只需要负责编写与业务逻辑相关的重要代码。 关于那些只是由于语法要求而添加的, 完全可以将其字节代码静态生成出来。 就曾经成为了顺序的一部分, 要么重新生成。 关于开发人员是完全通明的。 妥善使用Java字节代码的操纵技术, 可以更好的处理某一类开发成绩。

posted on 2011-04-05 06:07  jiyizhen3721  阅读(208)  评论(0编辑  收藏  举报