- 浏览: 100299 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
lyhainan:
谢谢分享
Oracle中Union与Union All的区别 -
eclipsejob:
这种写法,如果数据量大的情况下性能是不是就没法看了呢
Oracle 多个查询语句结果合并 -
ccs137517868:
我去,楼主是做DBA的吗,oracle的知识真多
Oracle维护常用SQL语句汇总 -
ccs137517868:
精彩,说的太详细了。
Oracle中Union与Union All的区别 -
宋建勇:
...
Oracle中Union与Union All的区别
Java 顾问 Dennis Sosnoski 在他的关于 Javassist 框架的三期文章中将精华部分留在了最后。这次他展现了 Javassist 对搜索-替换的支持是如何使对 Java 字节码的编辑变得像文本编辑器的“替换所有(Replace All )”命令一样容易的。想报告所有写入特定字段的内容或者对方法调用中参数的更改中的补丁吗?Javassist 使这变得很容易,Dennis 向您展示了其做法。
本系列的 第 4 部分和 第 5 部分讨论了如何用 Javassist 对二进制类进行局部更改。这次您将学习以一种更强大的方式使用该框架,从而充分利用 Javassist 对在字节码中查找所有特定方法或者字段的支持。对于 Javassist 功能而言,这个功能至少与它以类似源代码的方式指定字节码的能力同样重要。对选择替换操作的支持也有助于使 Javasssist 成为一个在标准 Java 代码中增加面向方面的编程功能的绝好工具。
第 5 部分介绍了 Javassist 是如何让您拦截类加载过程的 ―― 甚至在二进制类表示正在被加载的时候对它们进行更改。这篇文章中讨论的系统字节码转换可以用于静态类文件转换,也可以用于运行时拦截,但是在运行时使用尤其有用。
Javassist 提供了两种不同的系统字节码修改的处理方法。第一种技术是使用 javassist.CodeConverter
类,使用起来要稍微简单一些,但是可以完成的任务有很多限制。第二种技术使用 javassist.ExprEditor
类的自定义子类,它稍微复杂一些,但是所增加的灵活性足以抵销所付出的努力。在本文中我将分析这两种方法的例子。
系统字节码修改的第一种 Javassist 技术使用 javassist.CodeConverter
类。要利用这种技术,只需要创建 CodeConverter
类的一个实例并用一个或者多个转换操作配置它。每一个转换都是用识别转换类型的方法调用来配置的。转换类型可分为三类:方法调用转换、字段访问转换和新对象转换。
清单 1 给出了使用方法调用转换的一个例子。在这个例子中,转换只是增加了一个方法正在被调用的通知。在代码中,首先得到将要使用的 javassist.ClassPool
实例,将它配置为与一个翻译器一同工作 (正如在前面 第 5 部分 所看到的)。然后,通过 ClassPool
访问两个方法定义。第一个方法定义针对的是要监视的“set”类型的方法(类和方法名来自命令行参数),第二个方法定义针对的是 reportSet()
方法 ,它位于
TranslateConvert
类中,并会报告对第一个方法的调用。
有了方法信息后,就可以用 CodeConverter
insertBeforeMethod()
配置一个转换,以在每次调用这个 set 方法之前增加一个对报告方法的调用。然后所要做的就是将这个转换器应用到一个或者多个类上。在清单 1 的代码中,我是通过调用类对象的 instrument()
方法,在 ConverterTranslator
内部类的 onWrite()
方法中完成这项工作的。这将自动对从 ClassPool
实例中加载的每一个类应用这个转换。
public class TranslateConvert { public static void main(String[] args) { if (args.length >= 3) { try { // set up class loader with translator ConverterTranslator xlat = new ConverterTranslator(); ClassPool pool = ClassPool.getDefault(xlat); CodeConverter convert = new CodeConverter(); CtMethod smeth = pool.get(args[0]). getDeclaredMethod(args[1]); CtMethod pmeth = pool.get("TranslateConvert"). getDeclaredMethod("reportSet"); convert.insertBeforeMethod(smeth, pmeth); xlat.setConverter(convert); Loader loader = new Loader(pool); // invoke "main" method of application class String[] pargs = new String[args.length-3]; System.arraycopy(args, 3, pargs, 0, pargs.length); loader.run(args[2], pargs); } catch ... } } else { System.out.println("Usage: TranslateConvert " + "clas-name set-name main-class args..."); } } public static void reportSet(Bean target, String value) { System.out.println("Call to set value " + value); } public static class ConverterTranslator implements Translator { private CodeConverter m_converter; private void setConverter(CodeConverter convert) { m_converter = convert; } public void start(ClassPool pool) {} public void onWrite(ClassPool pool, String cname) throws NotFoundException, CannotCompileException { CtClass clas = pool.get(cname); clas.instrument(m_converter); } } } |
配置转换是一个相当复杂的操作,但是设置好以后,在它工作时就不用费什么心了。清单 2 给出了代码示例,可以作为测试案例。这里 Bean
提供了具有类似 bean 的 get 和 set 方法的测试对象, BeanTest
程序用这些方法来访问值。
public class Bean { private String m_a; private String m_b; public Bean() {} public Bean(String a, String b) { m_a = a; m_b = b; } public String getA() { return m_a; } public String getB() { return m_b; } public void setA(String string) { m_a = string; } public void setB(String string) { m_b = string; } } public class BeanTest { private Bean m_bean; private BeanTest() { m_bean = new Bean("originalA", "originalB"); } private void print() { System.out.println("Bean values are " + m_bean.getA() + " and " + m_bean.getB()); } private void changeValues(String lead) { m_bean.setA(lead + "A"); m_bean.setB(lead + "B"); } public static void main(String[] args) { BeanTest inst = new BeanTest(); inst.print(); inst.changeValues("new"); inst.print(); } } |
如果直接运行清单 2 中的 中的 BeanTest
程序,则输出如下:
[dennis]$ java -cp . BeanTest Bean values are originalA and originalB Bean values are newA and newB |
如果用 清单 1 中的 TranslateConvert
程序运行它并指定监视其中的一个 set 方法,那么输出将如下所示:
[dennis]$ java -cp .:javassist.jar TranslateConvert Bean setA BeanTest Bean values are originalA and originalB Call to set value newA Bean values are newA and newB |
每项工作都与以前一样,但是现在在执行这个程序时,所选的方法被调用时会有一个通知。
在这个例子中,可以用其他的方法容易地实现同样的效果,例如通过使用 第 4 部分 中的技术在实际的 set 方法体中增加代码。这里的区别是,在使用位置增加代码让我有了灵活性。例如,可以容易地修改 TranslateConvert.ConverterTranslator
onWrite()
方法来检查正在加载的类名,并只转换在我想要监视的类的清单中列出的类。直接在 set 方法体中添加代码无法进行这种有选择的监视。
系统字节码转换由于提供了灵活性而使其成为为标准 Java 代码实现面向方面的扩展的强大工具。在本文后面您会看到更多这方面的内容。
由 CodeConverter
处理的转换很有用,但是有局限性。例如,如果希望在调用目标方法之前或者之后调用一个监视方法,那么这个监视方法必须定义为 static void
并且必须先接受一个目标方法的类的参数,然后是与目标方法所要求的同样数量和类型的参数。
这种严格的结构意味着监视方法需要与目标类和方法完全匹配。举一个例子,假设我改变了 清单 1 中 reportSet()
方法的定义,让它接受一个一般性的 java.lang.Object
参数,想使它可以用于不同的目标类:
public static void reportSet(Object target, String value) { System.out.println("Call to set value " + value); } |
编译没有问题,但是当我运行它时它就会中断:
[dennis]$ java -cp .:javassist.jar TranslateConvert Bean setA BeanTest Bean values are A and B java.lang.NoSuchMethodError: TranslateConvert.reportSet(LBean;Ljava/lang/String;)V at BeanTest.changeValues(BeanTest.java:17) at BeanTest.main(BeanTest.java:23) at ... |
有办法绕过这种限制。一种解决方案是在运行时实际生成与目标方法相匹配的自定义监视方法。不过这要做很多工作,在本文中我不打算试验这种方法。幸运的是,Javassist 还提供了另一种处理系统字节码转换的方法。这种方法使用 javassist.ExprEditor
,与 CodeConverter
相比,它更灵活、也更强大。
用 CodeConverter
进行字节码转换与用 javassist.ExprEditor
的原理一样。不过, ExprEditor
方式也许更难理解一些,所以我首先展示基本原理,然后再加入实际的转换。
清单 3 显示了如何用 ExprEditor
来报告面向方面的转换的可能目标的基本项目。这里我在自己的 VerboseEditor
中派生了 ExprEditor
子类,重写了三个基本的类方法 ―― 它们的名字都是 edit()
,但是有不同的参数类型。如 清单 1 中的代码,我实际上是在 DissectionTranslator
内部类的 onWrite()
方法中使用这个子类,对从 ClassPool
实例中加载的每一个类,在对类对象的 instrument()
方法的调用中传递一个实例。
public class Dissect { public static void main(String[] args) { if (args.length >= 1) { try { // set up class loader with translator Translator xlat = new DissectionTranslator(); ClassPool pool = ClassPool.getDefault(xlat); Loader loader = new Loader(pool); // invoke the "main" method of the application class String[] pargs = new String[args.length-1]; System.arraycopy(args, 1, pargs, 0, pargs.length); loader.run(args[0], pargs); } catch (Throwable ex) { ex.printStackTrace(); } } else { System.out.println ("Usage: Dissect main-class args..."); } } public static class DissectionTranslator implements Translator { public void start(ClassPool pool) {} public void onWrite(ClassPool pool, String cname) throws NotFoundException, CannotCompileException { System.out.println("Dissecting class " + cname); CtClass clas = pool.get(cname); clas.instrument(new VerboseEditor()); } } public static class VerboseEditor extends ExprEditor { private String from(Expr expr) { CtBehavior source = expr.where(); return " in " + source.getName() + "(" + expr.getFileName() + ":" + expr.getLineNumber() + ")"; } public void edit(FieldAccess arg) { String dir = arg.isReader() ? "read" : "write"; System.out.println(" " + dir + " of " + arg.getClassName() + "." + arg.getFieldName() + from(arg)); } public void edit(MethodCall arg) { System.out.println(" call to " + arg.getClassName() + "." + arg.getMethodName() + from(arg)); } public void edit(NewExpr arg) { System.out.println(" new " + arg.getClassName() + from(arg)); } } } |
清单 4 显示了对 清单 2 中的 BeanTest
程序运行清单 3 中的 Dissect
程序所产生的输出。它给出了加载的每一个类的每一个方法中所做的工作的详细分析,列出了所有方法调用、字段访问和新对象创建。
[dennis]$ java -cp .:javassist.jar Dissect BeanTest Dissecting class BeanTest new Bean in BeanTest(BeanTest.java:7) write of BeanTest.m_bean in BeanTest(BeanTest.java:7) read of java.lang.System.out in print(BeanTest.java:11) new java.lang.StringBuffer in print(BeanTest.java:11) call to java.lang.StringBuffer.append in print(BeanTest.java:11) read of BeanTest.m_bean in print(BeanTest.java:11) call to Bean.getA in print(BeanTest.java:11) call to java.lang.StringBuffer.append in print(BeanTest.java:11) call to java.lang.StringBuffer.append in print(BeanTest.java:11) read of BeanTest.m_bean in print(BeanTest.java:11) call to Bean.getB in print(BeanTest.java:11) call to java.lang.StringBuffer.append in print(BeanTest.java:11) call to java.lang.StringBuffer.toString in print(BeanTest.java:11) call to java.io.PrintStream.println in print(BeanTest.java:11) read of BeanTest.m_bean in changeValues(BeanTest.java:16) new java.lang.StringBuffer in changeValues(BeanTest.java:16) call to java.lang.StringBuffer.append in changeValues(BeanTest.java:16) call to java.lang.StringBuffer.append in changeValues(BeanTest.java:16) call to java.lang.StringBuffer.toString in changeValues(BeanTest.java:16) call to Bean.setA in changeValues(BeanTest.java:16) read of BeanTest.m_bean in changeValues(BeanTest.java:17) new java.lang.StringBuffer in changeValues(BeanTest.java:17) call to java.lang.StringBuffer.append in changeValues(BeanTest.java:17) call to java.lang.StringBuffer.append in changeValues(BeanTest.java:17) call to java.lang.StringBuffer.toString in changeValues(BeanTest.java:17) call to Bean.setB in changeValues(BeanTest.java:17) new BeanTest in main(BeanTest.java:21) call to BeanTest.print in main(BeanTest.java:22) call to BeanTest.changeValues in main(BeanTest.java:23) call to BeanTest.print in main(BeanTest.java:24) Dissecting class Bean write of Bean.m_a in Bean(Bean.java:10) write of Bean.m_b in Bean(Bean.java:11) read of Bean.m_a in getA(Bean.java:15) read of Bean.m_b in getB(Bean.java:19) write of Bean.m_a in setA(Bean.java:23) write of Bean.m_b in setB(Bean.java:27) Bean values are originalA and originalB Bean values are newA and newB |
通过在 VerboseEditor
中实现适当的方法,可以容易地增加对报告强制类型转换、 instanceof
检查和 catch
块的支持。但是只列出有关这些组件项的信息有些乏味,所以让我们来实际修改项目吧。
清单 4对类的剖析列出了基本组件操作。容易看出在实现面向方面的功能时使用这些操作会多么有用。例如,报告对所选字段的所有写访问的记录器(logger)在许多应用程序中都会发挥作用。无论如何,我已经承诺要为您介绍如何完成 这类工作。
幸运的是,就本文讨论的主题来说, ExprEditor
不但让我知道代码中有什么操作,它还让我可以修改所报告的操作。在不同的 ExprEditor.edit()
方法调用中传递的参数类型分别定义一种 replace()
方法。如果向这个方法传递一个普通 Javassist 源代码格式的语句(在 第 4 部分中介绍),那么这个语句将编译为字节码,并且用来替换原来的操作。这使对字节码的切片和切块变得容易。
清单 5 显示了一个代码替换的应用程序。在这里我不是记录操作,而是选择实际修改存储在所选字段中的 String
值。在 FieldSetEditor
中,我实现了匹配字段访问的方法签名。在这个方法中,我只检查两样东西:字段名是否是我所查找的,操作是否是一个存储过程。找到匹配后,就用使用实际的 TranslateEditor
应用程序类中 reverse()
方法调用的结果来替换原来的存储。 reverse()
方法就是将原来字符串中的字母顺序颠倒并输出一条消息表明它已经使用过了。
public class TranslateEditor { public static void main(String[] args) { if (args.length >= 3) { try { // set up class loader with translator EditorTranslator xlat = new EditorTranslator(args[0], new FieldSetEditor(args[1])); ClassPool pool = ClassPool.getDefault(xlat); Loader loader = new Loader(pool); // invoke the "main" method of the application class String[] pargs = new String[args.length-3]; System.arraycopy(args, 3, pargs, 0, pargs.length); loader.run(args[2], pargs); } catch (Throwable ex) { ex.printStackTrace(); } } else { System.out.println("Usage: TranslateEditor clas-name " + "field-name main-class args..."); } } public static String reverse(String value) { int length = value.length(); StringBuffer buff = new StringBuffer(length); for (int i = length-1; i >= 0; i--) { buff.append(value.charAt(i)); } System.out.println("TranslateEditor.reverse returning " + buff); return buff.toString(); } public static class EditorTranslator implements Translator { private String m_className; private ExprEditor m_editor; private EditorTranslator(String cname, ExprEditor editor) { m_className = cname; m_editor = editor; } public void start(ClassPool pool) {} public void onWrite(ClassPool pool, String cname) throws NotFoundException, CannotCompileException { if (cname.equals(m_className)) { CtClass clas = pool.get(cname); clas.instrument(m_editor); } } } public static class FieldSetEditor extends ExprEditor { private String m_fieldName; private FieldSetEditor(String fname) { m_fieldName = fname; } public void edit(FieldAccess arg) throws CannotCompileException { if (arg.getFieldName().equals(m_fieldName) && arg.isWriter()) { StringBuffer code = new StringBuffer(); code.append("$0."); code.append(arg.getFieldName()); code.append("=TranslateEditor.reverse($1);"); arg.replace(code.toString()); } } } } |
如果对 清单 2 中的 BeanTest
程序运行清单 5 中的 TranslateEditor
程序,结果如下:
[dennis]$ java -cp .:javassist.jar TranslateEditor Bean m_a BeanTest TranslateEditor.reverse returning Alanigiro Bean values are Alanigiro and originalB TranslateEditor.reverse returning Awen Bean values are Awen and newB |
我成功地在每一次存储到 Bean.m_a
字段时,加入了一个对添加的代码的调用(一次是在构造函数中,一次是在 set 方法中)。我可以通过对从字段的加载实现类似的修改而得到反向的效果,不过我个人认为颠倒值比开始使用的值有意思得多,所以我选择使用它们。
本文介绍了用 Javassist 可以容易地完成系统字节码转换。将本文与上两期文章结合在一起,您应该有了在 Java 应用程序中实现自己面向方面的转换的坚实基础,这个转换过程可以作为单独的编译步骤,也可以在运行时完成。
要想对这种方法的强大之处有更好的了解,还可以分析用 Javassis 建立的 JBoss Aspect Oriented Programming Project (JBossAOP)。JBossAOP 使用一个 XML 配置文件来定义在应用程序类中完成的所有不同的操作。其中包括对字段访问或者方法调用使用拦截器,在现有类中添加 mix-in 接口实现等。JBossAOP 将被加入正在开发的 JBoss 应用程序服务器版本中,但是也可以在 JBoss 以外作为单独的工具提供给应用程序使用。
本系列的下一步将介绍 Byte Code Engineering Library (BCEL),这是 Apache Software Foundation 的 Jakarta 项目的一部分。BCEL 是 Java classworking 最广泛使用的一种框架。它使用与我们在最近这三篇文章中看到的 Javassist 方法的不同方法处理字节码,注重个别的字节码指令而不是 Javassist 所强调的源代码级别的工作。下个月将分析在字节码汇编器(assembler)级别工作的全部细节。
发表评论
-
反射泛型
2009-12-15 11:37 1857Java™ 5 扩展了 Java 语言类型系统以支持类、方法和 ... -
Java 编程的动态性,第 8 部分: 用代码生成取代反射
2009-12-15 11:33 1085从本系列前面的文章中,您了解到反射的性能比直接访问要慢许多倍, ... -
Java 编程的动态性,第 7 部分: 用 BCEL 设计字节码
2009-12-15 11:31 1082Apache Byte Code Engineer ... -
Java 编程的动态性,第 5 部分: 动态转换类
2009-12-15 11:27 1025在经过一段时间的休息 ... -
Java 编程的动态性, 第4部分: 用 Javassist 进行类转换
2009-12-15 11:25 1389厌倦了只能按编写好源代码的方式执行的 Java 类了吗?那么打 ... -
Java 编程的动态性,第3部分: 应用反射
2009-12-15 11:22 848命令行参数处理是一项令人厌烦的零碎工作,不管您过去已经处理过多 ... -
Java编程 的动态性,第 2部分: 引入反射
2009-12-15 11:20 761反射使您的程序代码能够接入装载到JVM中的类的内部信息,允许您 ... -
Java 编程的动态性,第 1 部分: 类和类装入
2009-12-15 11:12 871这一有关 Java 编程动态 ...
相关推荐
`Java Agent`允许我们对Java应用程序进行预处理,比如字节码注入,而`javassist`库则提供了一个方便的方式来动态地操作和修改Java类的字节码。 `Java Agent`是Java平台提供的一种机制,允许开发者在程序运行前或...
总的来说,javaagent和javassist的结合使用为Java开发者提供了强大的代码操作能力,允许我们在运行时对应用程序进行灵活的扩展和修改,极大地提升了开发的灵活性和效率。在实际项目中,如Spring AOP、AspectJ等框架...
动态代理在Java编程中是一种非常重要的技术,它允许我们在运行时创建对象的代理,从而可以在不修改原有代码的情况下,为对象添加额外的功能。本压缩包包含关于三种主要的动态代理实现方式:JDK动态代理、CGLIB以及...
Javassist在Java应用开发中扮演着重要的角色,尤其是在动态代理、AOP(面向切面编程)以及代码生成等场景下。 Javassist允许程序员在运行时动态修改类或创建新的类,而无需了解复杂的Java字节码指令集。通过提供一...
在Java世界里,这种技术通常被称为字节码工程,对于实现如AOP(面向切面编程)、动态代理和运行时代码优化等高级功能非常有用。 `javassist-3.18.1-GA.jar`是Javaassist的一个特定版本,GA代表“General ...
Javaassist是一个开源库,它允许在运行时修改Java类和创建新的类。这个库在Java世界里被广泛用于动态代理、AOP(面向切面编程)以及类的...理解这些知识点对于深入学习Java的动态性、网络调试和AOP编程具有重要意义。
Javaassist是一个开源库,主要用在Java平台上,用于在运行时动态修改类和类加载器。这个库在Java世界中扮演着重要的角色,因为它允许开发者在程序运行时对字节码进行操作,提供了对Java类的修改、创建以及分析的能力...
在Java中,我们可以使用JDK自带的动态代理或者第三方库如CGLIB、Javassist、ASM来实现。 **JDK动态代理**: JDK的动态代理主要依赖于`java.lang.reflect.Proxy`和`java.lang.reflect.InvocationHandler`两个类。...
Java字节码编辑是Java开发中的一个高级主题,它允许开发者在运行时修改或增强类的行为。`javassist`库正是这样一个工具,它为Java...学习并熟练掌握`javassist`,将极大地提升你在Java动态编程和字节码操作方面的技能。
在Java应用程序中,这种能力非常有用,特别是在进行AOP(面向切面编程)或者在无法重新编译源代码的情况下需要修改类的行为时。 Javaassist库的核心功能包括创建新的类、接口,以及对现有类进行修改。它可以读取....
通过Javassist,我们可以方便地生成和修改类的字节码,从而实现AOP(面向切面编程)等高级功能。它的主要特性包括: 1. 提供了类似于C/C++的API,使得字节码操作更加直观。 2. 支持处理类、接口、方法、字段等元数据...
Java编程思想是深入理解并掌握Java这门编程语言的关键,其中源码的分析与学习尤为重要。这个压缩包包含了几个在Java编程中常见的关联库,这些库对于理解和实践Java编程思想有着重要作用。 首先,我们来看看`...
Javaassist是一个开源库,它允许我们在运行时动态地修改或创建Java类。这个强大的工具广泛应用于框架、代理生成以及AOP(面向切面编程)等领域。在本文中,我们将深入探讨如何结合Javaassist和注解(Annotation)来...
Java程序的动态性是软件开发中的一个重要概念,它关乎程序在运行时的灵活性、可扩展性和适应性。这篇资料“提高Java程序动态性的一个新途径”可能探讨了如何通过各种技术手段来增强Java应用程序的动态特性。下面我们...
《Thinking in Java》是Bruce Eckel的经典Java编程书籍,它深入浅出地讲解了Java语言的核心概念和技术。这本书强调了“思考”在编程中的重要性,不仅提供了丰富的代码示例,还鼓励读者通过实践来理解Java的精髓。...
6. **性能优化**:尽管Javassist提供了强大的动态字节码修改能力,但在某些情况下,如大量重复的字节码操作,其性能可能不如直接使用Java反射API。因此,理解何时使用Javassist是关键,以确保最佳性能。 7. **示例...
这个库在Java编程中尤其有用,因为它允许程序员在不重新编译源代码的情况下修改、添加或删除类和方法。`javassist-3.7.ga.jar`是Javaassist的一个版本,ga代表“General Availability”,意味着这是一个稳定版本,...
6. **兼容性**:`javassist-3.18.0-ga`版本支持Java 5及以上版本,这意味着它可以广泛应用于各种Java项目,包括那些基于旧版JDK的项目。 7. **应用领域**:Javaassist常用于动态代理框架(如Spring AOP)、代码生成...
Javaassist是一个开源库,主要用在Java应用程序中动态修改类和方法的行为。它提供了一种在运行时分析、改变和增强类的能力,而无需重新编译。这个版本"javassist-3.15.0-GA"是Javaassist的一个特定发行版,用于支持...