Javassist是一个执行字节码操作的强而有力的驱动代码库。它允许开发者自由的在一个已经编译好的类中添加新的方法,或者是修改已有的方法。但是,和其他的类似库不同的是,Javassist并不要求开发者对字节码方面具有多么深入的了解,同样的,它也允许开发者忽略被修改的类本身的细节和结构。
字节码驱动通常被用来执行对于已经编译好的类的修改,或者由程序自动创建执行类等等等等相关方面的操作。这就要求字节码引擎具备无论是在运行时或是编译时都能修改程序的能力。当下有些技术便是使用字节码来强化已经存在的Java类的,也有的则是使用它来使用或者产生一些由系统在运行时动态创建的类。举例而言,JDO1.0规范就使用了字节码技术对数据库中的表进行处理和预编译,并进而包装成Java类。特别是在面向对象驱动的系统开发中,相当多的框架体系使用字节码以使我们更好的获得程序的范型性和动态性。而某些EJB容器,比如JBOSS项目,则通过在运行中动态的创建和加载EJB,从而戏剧性的缩短了部署EJB的周期。这项技术是如此的引人入胜,以至于在JDK中也有了标准的java.lang.reflect.Proxy类来执行相关的操作。
但是,尽管如此,编写字节码对于框架程序开发者们而言,却是一个相当不受欢迎的繁重任务。学习和使用字节码在某种程度上就如同使用汇编语言。这使得于大多数开发者而言,尽管在程序上可以获得相当多的好处,可攀登它所需要的难度则足以冷却这份热情。不仅如此,在程序中使用字节码操作也大大的降低了程序的可读性和可维护性。
这是一块很好的奶油面包,但是我们却只能隔着橱窗流口水…难道我们只能如此了吗?
所幸的是,我们还有Javassist。Javassist是一个可以执行字节码操作的函数库,可是尽管如此,它却是简单而便与理解的。他允许开发者对自己的程序自由的执行字节码层的操作,当然了,你并不需要对字节码有多深的了解,或者,你根本就不需要了解。
API Parallel to the Reflection API
Javassist的最外层的API和JAVA的反射包中的API颇为类似。它使你可以在装入ClassLoder之前,方便的查看类的结构。它主要由CtClass,,CtMethod,,以及CtField几个类组成。用以执行和JDK反射API中java.lang.Class,,java.lang.reflect.Method,, java.lang.reflect.Method .Field相同的操作。这些类可以使你在目标类被加载前,轻松的获得它的结构,函数,以及属性。此外,不仅仅是在功能上,甚至在结构上,这些类的执行函数也和反射的API大体相同。比如getName,getSuperclass,getMethods,,getSignature,等等。如果你对JAVA的反射机制有所了解的话,使用Javassist的这一层将会是轻松而快乐的。
接下来我们将给出一个使用Javassist来读取org.geometry.Point.class的相关信息的例子(当然了,千万不要忘记引入javassist.*包):
1. ClassPool pool = ClassPool.getDefault();
2. CtClass pt = pool.get("org.geometry.Point");
3. System.out.println(pt.getSuperclass().getName());
其中,ClassPool是CtClass 的创建工厂。它在class path中查找CtClass的位置,并为每一个分析请求创建一个CtClass实例。而“getSuperclass().getName()”则展示出org.geometry.Point.class所继承的父类的名字。
但是,和反射的API不尽相同的是,Javassist并不提供构造的能力,换句话说,我们并不能就此得到一个org.geometry.Point.class类的实例。另一方面,在该类没有实例化前,Javassist也不提供对目标类的函数的调用接口和获取属性的值的方法。在分析阶段,它仅仅提供对目标类的类定义修改,而这点,却是反射API所无法做到的。
举例如下:
4. pt.setSuperclass(pool.get("Figure"));
这样做将修改目标类和其父类之间的关系。我们将使org.geometry.Point.clas改继承自Figure类。当然了,就一致性而言,必须确保Figure类和原始的父类之间的兼容性。
而往目标类中新增一个新的方法则更加的简单了。首先我们来看字节码是如何形成的:
5. CtMethod m = CtNewMethod.make("public int xmove(int dx) { x += dx; }", pt);
6. pt.addMethod(m);
CtMethod类的让我们要新增一个方法只需要写一段小小的函数。这可是一个天大的好消息,开发者们再也不用为了实现这么一个小小的操作而写一大段的虚拟机指令序列了。Javassist将使用一个它自带的编译器来帮我们完成这一切。
最后,千万别忘了指示Javassist把已经写好的字节码存入到你的目标类里:
7. pt.writeFile();
writeFile方法可以帮我们把修改好了的定义写到目标类的.class文件里。当然了,我们甚至可以在该目标类加载的时候完成这一切,Javassist可以很好的和ClassLoader协同工作,我们不久就将看到这一点。
Javassist并不是第一套用以完成从代码到字节码的翻译的函数库。Jakarta的BCEL也是一个比较知名的字节码引擎工具。但是,你却无法使用BCEL来完成代码级别的字符码操作。如果你需要在一个已经编译好的类中添加一个新的方法,假如你用的是BCEL的话,你只能定义一段由那么一大串字符码所构成的指令序列。正如上文所说,这并不是我们所希望看到的。因此,就此方面而言,Javassis使用代码的形式来插入新的方法实在是一大福音。
Instrumenting a Method Body
和方法的新增一样,对于一个类的方法的其他操作也是定义在代码层上的。换而言之,尽管这些步骤是必须的,开发者们也同样无须直接对虚拟机的指令序列进行操作和修改,Javassis将自动的完成这些操作。当然了,如果开发者认为自己有必要对这些步骤进行管理和监控,或者希望由自己来管理这些操作的话,Javassist同样提供了更加底层的API来实现,不过我们在这篇文章中将不会就此话题再做深入探讨。恩,尽管从结构而言,它和BCEL的字节码层API差不多。
设计Javassist对目标类的子函数体的操作API的设想立足与ASPect-Oriented Programming(AOP)思想。Javassist允许把具有耦合关系的语句作为一个整体,它允许在一个插入语句中调用或获取其他函数或者及属性值。它将自动的对这些语句进行优先级分解并执行嵌套操作。
如下例所示,清单1首先包含了一个CtMethod,它主要针对Screen类的draw方法。然后,我们定义一个Point类,该类有一个move操作,用来实现该Point的移动。当然了,在移动前,我们希望可以通过draw方法得到该point目前的位置,那么,我们需要对该move方法加增如下的定义:
{ System.out.println("move"); $_ = $proceed($$); }
这样,在执行move之前,我们就可以打印出它的位置了。请注意这里的调用语句,它是如下格式的:
$_ = $proceed($$);
这样我们就将使用原CtMethod类中的process()对该point的位置进行追踪了。
基与如上情况,CtMethod的关于methord的操作其实被划分成了如下步骤,首先,CtMethod的methord将扫描插入语句(代码)本身。一旦发现了子函数,则创建一个ExprEditor实例来分析并执行这个子函数的操作。这个操作将在整个插入语句执行之前完成。而假如这个实例存在某个static的属性,那么methord将率先检测对插入语句进行检测。然后,在执行插入到目标类---如上例的point类---之前,该static属性将自动的替换插入语句(代码)中所有的相关的部分。不过,值得注意的是,以上的替换操作,将在Javassist把插入语句(代码)转变为字节码之后完成。
Special Variables
在替换的语句(代码)中,我们也有可能需要用到一些特殊变量来完成对某个子函数的调用,而这个时候我们就需要使用关键字“$”了。在Javassist中,“$”用来申明此后的某个词为特殊参数,而“$_”则用来申明此后的某个词为函数的回传值。每一个特殊参数在被调用时应该是这个样子的“$1,$2,$3…”但是,特别的,目标类本身在被调用时,则被表示为“$0”。这种使用格式让开发者在填写使用子函数的参数时轻松了许多。比如如下的例子:
{ System.out.println("move"); $_ = $proceed($1, 0); }
请注意,该子函数的第2个参数为0。
另外一个特殊类型则是$arg,它实际上是一个容纳了函数所有调用参数的Object队列。当Javassist在扫描该$arg时,如果发现某一个参数为JAVA的基本类型,则它将自动的对该参数进行包装,并放入队列。比如,当它发现某一个参数为int类型时,它将使用java.lang.integer 类来包装这个int参数,并存入参数队列。和Java的反射包:java.lang.reflect.Methord类中的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. 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();
以上代码的前四行不难理解,Javassist首先对Point中的move方法做了个拷贝,并创建了一个新的函数。然后,它把存在与Point类中的原move方法更名为“_orig”。接下来,让我们关注一下程序第五行中的几个参数:第一个参数指示该函数的在执行的最初部分需要先打印一段信息,然后执行子函数proceed()并返回结果,这个和move方法差不多,很好理解。第二个参数则只是申明该子函数所在的类的位置。这里为this即为Point类本身。第三个参数,也就是“m1.getName()”则定义了这个新函数的名字。
Javassist也同样具有其他的操作和类来帮助你实现诸如修改某一个属性的值,改变函数的回值,并在某个函数的执行后补上其他操作的功能。您可以浏览www.javassist.org以获得相关的信息。
示例:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
public class Test {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//设置目标类的路径,确保能够找到需要修改的类,这里我指向firestorm.jar
//解包后的路径
pool.insertClassPath("d:/work/firestorm/firestorm") ;
//获得要修改的类
CtClass cc = pool.get("com.codefutures.if.if");
//设置方法需要的参数
CtClass[] param = new CtClass[3] ;
param[0] = pool.get("java.security.PublicKey") ;
param[1] = pool.get("byte[]") ;
param[2] = pool.get("byte[]") ;
//得到方法
CtMethod m = cc.getDeclaredMethod("a", param);
//插入新的代码
m.insertBefore("{return true ;}") ;
//保存到文件里
cc.writeFile() ;
}
}
分享到:
相关推荐
总结来说,"javassistDemo.zip"是一个实践教程,通过实例演示了如何使用Javaassist库来动态修改Java类,包括插入新的方法和改变方法的实现。这对于理解和掌握Java运行时代码修改技术,尤其是对于那些需要在运行时...
这个文件很可能是Javassist的一个特定版本,包含必要的类库和可能的示例代码。要开始使用,可以按照以下步骤操作: 1. **导入依赖**:在Java项目中,将下载的Javassist库添加到类路径,如果是Maven项目,可以在pom....
- **文档**:官方提供详细的API文档和教程,帮助开发者快速上手。 - **示例代码**:通过阅读和实践示例代码,可以更好地理解Javaassist的工作原理和使用方式。 - **社区支持**:Javaassist有一个活跃的社区,遇到...
Javaassist 是一个强大的 Java 字节码操作库,它允许开发者在运行时动态修改类或创建新的类。...通过阅读提供的文档、示例和源代码,开发者可以深入了解如何有效地利用这个库来提升他们的Java项目。
8. **学习资源**:为了更好地理解和使用Javassist,开发者可以参考其官方文档,以及各种在线教程和示例代码。社区支持也相当活跃,许多开发者分享了他们的经验和技巧。 总的来说,Javassist是一个功能强大且灵活的...
初学者可以从官方文档、教程和示例代码开始,逐步熟悉其API和使用方式。理解如何创建、修改和加载类是关键,同时注意版本间的兼容性和性能差异。 总的来说,Javaassist是Java开发中一个强大的工具,尤其在需要动态...
JavaAgent是一种强大的技术,它允许在...项目中可能包含示例代码、教程文档,以及一系列的练习,帮助你掌握这些技术。完成这个项目后,你将在字节码编程和JavaAgent应用方面具备坚实的基础,能够解决更高级的编程挑战。
本教程主要关注Struts 2的基础知识和简单示例程序。 在Struts 2框架中,核心组件`struts2-core-2.5.18.jar`是必不可少的,它包含了框架的主要功能,如Action类、配置管理、拦截器链和结果处理等。`freemarker-...
通过阅读和运行 "javassist-example" 中的示例,你可以更好地掌握 Javaassist 的用法,并将其应用到自己的项目中。无论是用于调试、性能优化还是复杂逻辑的实现,Javaassist 都是一个强大的工具。记得结合官方文档和...
Robotium的教程还涵盖了测试用例管理,使得测试人员能够更有效地组织和管理测试用例。FAQ部分为常见的问题提供了快速参考,而Tips则分享了一些提高测试效率的技巧和最佳实践。 总之,Robotium作为一个测试框架,为...
通过运行这些示例,开发者能够更直观地了解Ibatis和Mybatis在实际应用中的工作流程,并从中学习到如何配置、使用和优化这两个框架。 总的来说,深入研究Ibatis和Mybatis的源码,不仅能提升我们对数据库操作的理解,...
- 另外还需要导入以下三个JAR包来避免运行时可能出现的异常:`commons-io-1.3.2.jar`, `commons-fileupload-1.2.1.jar`, `javassist-3.7.ga.jar`(此包位于`struts2-blank-2.2.1.war`示例工程的`web-inf/lib`目录下...
3. **教程和示例**:可能包含一些示例项目,帮助初学者快速上手。 4. **发行说明**:记录了该版本的改进、修复的bug以及可能的问题。 综上所述,Hibernate 3.6.10 Final是一个重要的Java ORM框架版本,它提供了许多...
Java类重载是面向对象编程中的一个重要概念,它允许我们在同一个类中定义多个同名方法,...如果你需要更深入地学习这个主题,建议查阅相关的Java教程,实践编写和测试类重载的方法,同时探索如何配置和使用热更新工具。
本教程将详细介绍如何使用工具jclasslib和编写Java程序来修改.class文件的内容。 首先,我们需要了解.jclasslib工具。jclasslib是一款强大的类文件查看器,它允许开发者以图形化的方式查看和分析Java字节码。在给定...
其中,WEB-INF文件夹中包含了web.xml文件和lib文件夹,lib文件夹中包含了Struts2.3.8所需的所有jar包。 2. 修改web.xml 在web.xml文件中添加以下配置: ```xml <filter-name>struts2 <filter-class>org.apache...
使用Seasar2的基本功能(如S2Container和S2AOP)时,需要在CLASSPATH下包含一系列特定的JAR文件,包括aopalliance、commons-logging、javassist、ognl和s2-framework等。 如果要利用Seasar2的扩展功能,如S2JTA、S2...
描述中提到的“博文链接:https://jsufly.iteye.com/blog/749394”可能提供了一个关于如何实现Java和AJAX树的详细教程或示例。由于无法直接查看这个链接,我们可以根据常见实践来探讨相关知识点。 1. **Java后端**...
9. mysql-connector-java.jar:MySQL数据库的驱动,如果示例或教程涉及到MySQL数据库的连接。 10. cglib-nodep.jar:代码生成库,Hibernate在某些情况下会用到。 **相关知识点:** 1. **Hibernate框架**:是一个...
此外,博文链接提供的资源可能包含Struts2的示例代码和详细的使用教程,这对于初学者理解Struts2的工作原理和实际应用非常有帮助。通过学习和实践,开发者可以掌握如何有效地利用Struts2框架来提升Java web应用的...