`
li-yuan
  • 浏览: 68586 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

Javassite:使字节码工程变得简单(译)

阅读更多
--比字节码抽象度更高的java源代码级进行字节码工程

 清源(mote_li)译  
原文:http://sys-con.com/story/?storyid=38672&DE=1
Javassite
是一个强大的新的用于字节码工程的库,它允许开发人员给编译过的类增加方法、修改方法等等。不像其他的类似的包,你不需要知道java字节码的知识也不用了解一个class文件的结构,就可以实现上面描述的功能。

节码工程被用于操作、修改一个编译过的类或者用编程的方式新建一个类。节码工程会发生在运行时或者编译的时候,一些技术使用节码操作优化或提高现存class的性能,另一个些技术却使用节码操作来使现存的class更易于使用或者用来避免笨重的代码生成。例如:JDO 1.0 (Java Data Objects)规范需要往简单java类添加进行数据库持久化操作的预编译java字节码;在面向方面编程中,一些新的框架使用节码工程来实现对java类的横切功能;EJB容器,像jboss,在运行期动态的生成新的类以避免EJB代码生成和预编译步骤,这样可以显著的提高开发周期;这相当于jdk在它的java.lang.reflect.Proxy内中所做的字节码操作。

字节码操作对于框架开发者来说是一项复杂和不受欢迎的任务,因为需要付出很大的代价。学习字节码和学习汇编语言很类似,对于开发者来说学习曲线可能会很陡峭,还不仅仅这些,直接写字节码对于维护来说也是一个挑战,因为节码很难阅读和理解。Javassist是一个简化字节码操作的库,它允许开发人员在只有一点字节码或者完全没有字节码知识的情况下完全操作字节码,给开发人员提供了一种细粒度地控制控制字节码的手段。

Reflection API类似的API

Javassite的第一部分API类似于Reflection API,它让你能够在类被类装载器装载前,查看类的结构。这部分api包括CtClass,CtMehodhCtField,这些类和Reflection API中的java.lang.Class java.lang.reflect.Method, Field起一样的作用,只是这三个类分别用于描述一个类,一个方法,一个字段的时候这个被描述的类并没有被装载。他们提供了很多类似Reflection API的方法,例如getName, getSuperclass, getMethods, getSignature等等。下面的代码读取org.geometry.Point类,分析这个类的定义,然后打印出它的父类的名字(这篇文章假定,已经引入了javassist.* :

1. ClassPool pool = ClassPool.getDefault();

2. CtClass pt = pool.get("org.geometry.Point");

3. System.out.println(pt.getSuperclass().getName());

ClassPool对象是CtClass的工厂,它在指定的类路径里搜索每一个类文件,并为一个搜索到的类文件建立一个单例CtClass对象,get方法根据指定的类名返回用于描述该类的CtClass对象。

既然当一个CtClass对象用于描述一个类时这个类并没有被装载,Javassite API也就没有提供新建一个实例的方法(newInstance方法)、调用方法的方法(invoke 方法)、访问一个字段的方法。另一方面这些api却提供了一些用于修改类定义的方法,例如CtClass中的setSuperclass方法用于改变类的父类,Reflection API不支持这种更改父类的操作。一个例子:

4. pt.setSuperclass(pool.get("Figure"));

Point类定义的修改将使Point类继承Figure,出于一致性的考虑,在上面的例子中我们假设Figure类和Point原本的父类是兼容的。

Point增减一个方法也是可能的:

5. CtMethod m = CtNewMethod.make("public int xmove(int dx) { x += dx; }", pt);

6. pt.addMethod(m);

使用一段java源代码文本作为参数通过CtNewMethod类的make所创建的CtMothed对象用于表示需要增加的方法,开发人员不需要手工的写一串java虚拟机指令,javassite使用包含在内部的一个专用的java编译器编译java源代码文本。最终,为了把上面的修改反映出来,writeFile方法将被调用。

7. pt.writeFile();

CtClasswriteFile方法能够把修改后的类定义写到类文件里。Javassite能够和类装载器一起工作,我们将在后面讨论这个问题。Javassite不是实现字节码翻译器的第一个类库,例如,Jakarta BCEL就是一个非常流行的字节码工程库,但是,你不能使用Jakarta BCEL实现源代码文本级的操作。如果你想给一个被编译过的类增加一个方法,就必须把方法体的字节码指令列出来,而使用Javassite只需要指定方法体的java源代文本就可以了,就像上面描述的那样。

编辑方法体

编辑方法体的手段同样是使用源代码文本来描述的。开发人员不需要直接操作虚拟机指令。虚拟机指令级操作不是被限制的,Javassite提供了多种典型的手段,如果开发人员需要进行一些Java源代码不能描述的细粒度的操作,他们可以使用Javassite的底层API,本文不会论述这些API,这些APIJakarta BCEL很类似。

Javassite用于编辑方法体的API是基于AOP思想来进行设计的, Javassite可以支持一些特殊表达式用来替换原来的方法体的方法调用的表达式和访问字段的表达式。

例如,列表1

1. ClassPool pool = ClassPool.getDefault();

2. CtClass cc = pool.get("Screen");

3. CtMethod cm = cc.getDeclaredMethod("draw", new CtClass[0]);

4. cm.instrument(new ExprEditor() {

5.    public void edit(MethodCall m) throws CannotCompileException {

6.    if (m.getClassName().equals("Point")

7.      && m.getMethodName().equals("move"))

8.      m.replace("{ System.out.println(\"move\"); $_ = $proceed($$); }");

9.   }

10. });

11. cc.writeFile();

首先得到一个用来描述Screendraw方法的CtMethod对象,然后这个对象在draw方法体内查找所有调用Pointmove方法的表达式,所有被找到的表达式都将被下面的语句替换掉。

{ System.out.println("move"); $_ = $proceed($$); }

所以在调用move方法前一调信息将被打印,这条语句使用了一种特殊的语法。

$_ = $proceed($$);

表示使用原来的参数调用原来的方法

使用CtMethod编辑方法,先搜索方法体,当发现方法调用表达式时就会调用给定ExprEditor对象的edit方法。传递给edit方法的参数是一个代表所找到表达式的MethodCall对象,这个对象提供了一些方法用来得到表达式的一些静态属性,edit方法首先判断表达式所调用是否是Point对象的move方法,如果是就用打印消息的语句块替换原来方法调用的语句,这些用来进行替换的语句块在替换前是被Javassite自代的编译器编译成字节码的。

特殊变量

在这些用来进行替代的语句中有许多由$开头的特殊变量,例如:$_代表被替换表达式的结果。$$在替换语句中用来代表被替换表达式中方法调用的参数列表,$proceed代表原来被调用的方法名。原表达式中方法调用的参数分别由$1,$2,$3...等代表,被调用的目标对象由$0代表。当开发者需要改变调用参数的时候这些特殊变量就变得非常有用。例如,用于替换的表达式如下

{ System.out.println("move"); $_ = $proceed($1, 0); }

调用move方法的第二表参数将被置为零。

另一个特殊变量是$args,这是一个对象数组,包含了所有调用原方法的参数。如果调用的参数是简单类型,将被自动装箱成对应的包裹类的对象,然后保存到这个对象数组里。例如,参数是一个int型的值,将被包裹成一个java.lang.Integer 对象被放到$args所代表的对象数组里。当使用java.lang.reflect.Method类中的invoke来调用方法的时候,$args是非常有用的。

Javassist也允许在方法体的开始和结尾插入代码片断,例如,可以通过insertBefore方法把给定的代码块插入到方法体的开始。

1. ClassPool pool = ClassPool.getDefault();

    2. CtClass cc = pool.get("Screen");

    3. CtMethod cm = cc.getDeclaredMethod("draw", new CtClass[0]);

    4. cm.insertBefore("{ System.out.println($1); System.out.println($2); }");

    5. cc.writeFile();

这个例子在draw方法体的开始部分插入代码片断,把调用draw方法的两个变量的值打印出来,$1$2是两个特殊变量,分别代表着调用draw方法的两个参数。

在传递给CtMethodsetMethod方法作为参数的源代文本中$开头的标识符也是特殊变量。接下来的例子增加了两个包裹方法∶

1. CtClass cc = sloader.get("Point");

    2. CtMethod m1 = cc.getDeclaredMethod("move");

    3. CtMethod m2 = CtNewMethod.copy(m1, cc, null);

    4. m1.setName(m1.getName() + "_orig");

    5. m2.setBody("{ System.out.println("call"); return $proceed($$);

                  }", "this", m1.getName());

    6. cc.addMethod(m2);

    7. cc.writeFile();

程序首先构造一个CtMethod对象来表示Piont类的move方法,再用拷贝的方式构造一个同样代表move方法的CtMethod对象,通过第一个CtMethod对象把Piont类的move方法改名为move_orig,接着通过第二个CtMethod对象的setBody方法修改方法体方法名不变仍然是move,把修改后的move添给Piont类,修改后的move先打印一条消息后再调用move_orig,它成为了原来move方法的包裹方法。setBody方法的第二个参数用于指定$proceed($$)所代表的目标对象,第三个参数是指定$proceed所代表的方法名。

Javassite还提供了为数众多的帮助类和函数,通过它们你可以替换字段访问,改变方法调用,简化在方法体的开始或者结束插入代码。你可以访问 http://www.javassist.org/以获得更多的信息和教程。

执行效率

一些开发者担心使用Javassist在运行时修改类的效率。认为包含在javassite中定制的编译器会使运行效率变得极为低下,但是,在运行时真正耗时的是处理以$开头的特殊变量,相反定制编译器能够生成优化的字节码来减低对运行效率的影响。例如,开发者使用CtMethod类来替换方法调用表达式,如果替换的语句块和被原来的语句块具有等价的效果,那么只是在替换那个表达式的时候需要多付出一个额外循环的代价而已。

ClassLoader

Javassite可以和ClassLoader可以结合起来运行时在类被装载前修改类的字节码。在java里开发人员可以定制ClassLoader来定制类的装载过程,javassite可以利用这个机制在类被装载的过程中修改类定义。

运行时修改和装载类的简单方法就是把调用CtClass对象writeFile方法替换成调用toClass方法,前面的例子中调用writeFile把修改后的类定义写回磁盘上,现在调用toClass方法通过Javassite定制的类装载器装载类,这个方法返回被装载类对应的java.lang.Class对象。例如列表2中的代码动态建立了一个Hello类,并给它增加say()方法,然后装载Hello类并调用say方法。

  列表2

  1. ClassPool pool = ClassPool.getDefault();

    2. CtClass ch = pool.makeClass("Hello");

    3. CtClass ci = pool.get("IHello");

    4. ch.addInterface(ci);

    5. CtMethod m = CtNewMethod.make( "public void say() {System.out.println(\"Hello\"); }", ch);

    6. ch.addMethod(m);

    7. Class h = ch.toClass();

    8. IHello obj = (IHello)h.newInstance();

    9. obj.say();

IHello接口的定义如下

       1. public interface IHello {

        2.   void say();

        3. }

使用ClassLoaders的技巧

使用toClass非常简单但是必须小心。由于java类装载器的规则可能使开发者非常迷惑或者是遇上ClassCastException异常。例如在列表2的代码中我们在装载Hello类前先用toClass装载IHello接口,修改代码如下:

:

    7. Class ih = ci.toClass();

    8. Class h = cc.toClass();

    9. IHello obj = (IHello)h.newInstance();

    10. obj.say();

这样的修改将在第9行触发ClassCastException异常,这是因为第9行出现的IHello接口在Hello类的类定义不同于IHello接口的类定义。

为了理解这样的情况,你必须知道java里可以共存多重类装载器,这些多重类装载器组成树形结构,除了根类装载器外其他类装载器都有一个父类装载器,正常情况下类装载动作会按类装载器的层次关系委托给相应层次的类装载器。

被两个独立的类装载器装载的两个类就算同样的名称和类定义也会变成两个完全不同的类,因为类是不同的,一个类的实例就不能被赋给代表另一个类的变量。在前面的例子中,IHello在第7行被Javassist定制的类装载器(这个类装载器被称为LJ)装载,第8行装载Hello类的时候,ClassLoader缓存了IHello类,Hello是作为这个IHello接口的实现被装载的,这样Hello和所实现的接口都是被LJ装载的。

9行的IHello是被java缺省的类装载器(我们称为LP)装载的。由于类装载器LP装载LJLP就是LJ的父装载器。h.newInstance()创建的对象所实现的IHello接口是被LJ装载而不是被LP装载的,所以第9行会触发异常。如果把第7行删掉,那么第8LJ就会委托自己的父装载器去装载Hello类所实现的IHello接口。这样第9行的强制转化就不触发异常,Hello类被LJ装载,IHelloLP装载符合java类转载机制的层次关系,所以可以成功的进行强制转化。

为了避免ClassCastException带来的问题,Javassite提供另一种类装载的方式,允许开发人员完全控制类装载行为。开发人员可以定义一些事件监听器,每当客户有装载类的请求的时候就会触发这些事件监听器,可以在适当的时候对需要装载的类进行修改。

总结

Javassite是一个强大的可以在装载时或者运行时操作字节码的JAVA类库。与其他类时的类库相比,Javassite使用比字节码抽象度更高的java源代码。使用Javassite相当的简单,完全不需要详细的掌握字节码指令。

分享到:
评论

相关推荐

    javassist:Java字节码工程工具包

    Javassist(JAVA编程ASSISTant)使Java字节码操作变得简单。 它是一个用Java编辑字节码的类库。 它使Java程序可以在运行时定义新类,并在JVM加载它时修改类文件。 与其他类似的字节码编辑器不同,Javassist提供了两...

    可变字节码

    可变字节码(Variable Byte Code)是一种针对大数据存储优化的压缩算法,它在处理大量数据时能够有效地节省存储空间,提高数据传输效率。在理解可变字节码之前,我们首先需要了解基本的字节码概念。字节码通常是指...

    发布新程序:java 字节码查看器

    标题 "发布新程序:java 字节码查看器" 暗示了这是一个关于Java字节码分析工具的项目。在Java编程中,字节码是Java虚拟机(JVM)执行的中间代码,它是由Java编译器将源代码编译成的。这个程序可能允许开发者查看并...

    java源码:JAVA字节码操作库 BCEL.zip

    Java字节码操作库BCEL(Byte Code Engineering Library)是一个强大的工具,用于分析、修改和创建Java类文件。它是Apache软件基金会的Jakarta项目的一部分,为开发者提供了对字节码的底层控制,使得开发者能够在运行...

    Java字节码工程工具包.zip

    Javassist(JAVA 编程助手)使 Java 字节码操作变得简单。它是一个用于编辑 Java 字节码的类库它使 Java 程序能够在运行时定义新类并在 JVM 加载类文件时对其进行修改。与其他类似的字节码编辑器不同,Javassist ...

    字节码实战包含class,字节码.zip

    它们可以帮助开发者查看类文件的结构,理解字节码指令的含义,甚至修改字节码以实现调试、优化或逆向工程的目的。 5. **字节码优化**: 通过理解字节码,开发者可以进行针对性的优化,比如减少对象创建、优化循环...

    免费开源!!Java字节码工程工具包

    Javassist(JAVA 编程助手)使 Java 字节码操作变得简单。它是Java中用于编辑字节码的类库;它使 Java 程序能够在运行时定义新类,并在 JVM 加载时修改类文件。与其他类似的字节码编辑器不同,Javassist 提供两个...

    java字节码加密

    Java字节码加密是保护Java应用程序源代码安全的重要技术手段,主要是为了防止恶意用户逆向工程分析、篡改或盗取程序的核心逻辑。在Java中,字节码(Bytecode)是程序经过编译后的中间表示,可以直接由Java虚拟机...

    JAVA字节码操作库 BCEL

    **JAVA字节码操作库 BCEL** BCEL(Byte Code Engineering Library)是Java开发的一个重要工具,主要用于处理Java字节码。它为开发者提供了一种深入理解与操作Java类文件的底层机制,允许分析、创建、修改和优化字节...

    Recaf一个现代Java字节码编辑器

    字节码编辑器允许开发者在不触及原始源代码的情况下对程序进行修改,这对于调试、优化、逆向工程和插件开发等任务非常有用。 **2. Recaf的功能特性** - **图形化界面**:Recaf采用了现代GUI设计,使得用户可以方便...

    轻松看懂Java字节码.pdf

    标题《轻松看懂Java字节码.pdf》中隐藏的知识点是理解Java字节码的重要性及如何轻松掌握。描述中提到Java字节码是...通过实践和使用相关工具,可以使得阅读和分析字节码变得更加容易,从而更好地优化和维护Java程序。

    javassist,Java字节码工程工具包.zip

    Java字节码工程工具包,通常被称为Javassist,是一个开源项目,专为Java开发者提供了一种方便的方式来处理和操作字节码。Javassist在Java应用开发中扮演着重要的角色,尤其是在动态代理、AOP(面向切面编程)以及...

    修改字节码 jclasslib

    2. **颜色编码**:为了便于理解和分析,jclasslib对字节码指令使用了不同的颜色,使代码更易读。 3. **反汇编视图**:提供反汇编的字节码到可读的Java源代码形式,有助于理解字节码的执行逻辑。 4. **比较类文件**:...

    JByteMod-1.6.1(java字节码编辑器)简介及下载

    **JByteMod** 是一款专为Java开发者设计的字节码编辑器,它允许用户对JAR文件进行深度操作和修改。在Java编程环境中,字节码是Java虚拟机(JVM)执行的二进制代码,而JByteMod提供了一个用户友好的界面,使得开发者...

    javassist, Java字节码工程工具包.zip

    javassist, Java字节码工程工具包 Java字节码工程工具包 版本 3版权所有( C ) 1999 -2017按 Shigeru Chiba,保留所有权利。Javassist ( Java编程助手) 使Java字节码操作简单。 它是一个类库,用于在Java中编辑字节码

    jvm字节码自动加载

    在Java虚拟机(JVM)中,字节码自动加载是一项关键功能,它使得Java程序能够在运行时动态地发现和加载类。字节码是由Java源代码编译而成的二进制格式,它包含了类和接口的信息。了解JVM如何自动加载字节码对于深入...

    Android字节码插桩

    ASM库使得这个过程变得相对简单,因为开发者可以直接操纵字节码,而无需理解底层的二进制格式。 **ASM在Android插桩中的应用** 1. **性能监控**:ASM可以用来插入性能监控代码,如计算方法的执行时间,统计CPU和...

    一种Java字节码保护技术的研究和实现.pdf

    5. 自定义类加载器:使用自定义的类加载器来加载Java字节码,使得反编译变得困难。 基于JVMTI的Java字节码保护技术 -------------------------------- JVMTI(Java Virtual Machine Tool Interface)是Java虚拟机...

    JavaBytecodeChange:Java字节码栈堆叠转换

    JavaBytecodeChange目的:在编译时重写字节码并使崩溃的反编译器生成的源代码==================用法-编译后,编译测试文件并检查执行内容。 $ mvn package$ javac Test.java$ java Test-混淆生成的类文件$ java -...

Global site tag (gtag.js) - Google Analytics