Javassist之内省与定制(五)

在前几个篇章介绍Javassist如何修改方法体,本篇介绍javassist内省与定制的剩余部分。

1. 添加新的方法或属性

添加方法

Javassist允许用户创建从零开始创建一个新的方法和构造函数。CtNewMethod和CtNewConstructor提供了多种工厂方法,它们都是用来创建CtMethod或者CtConstrutor对象的静态方法。特别的,make()方法可以根据所给的源代码文本创建一个CtMethod或者CtConstructor对象。

例如下面这个程序:

CtClass point = ClassPool.getDefault().get("Point");
CtMethod m = CtNewMethod.make(
                 "public int xmove(int dx) { x += dx; }",
                 point);
point.addMethod(m);

添加一个pulbic方法xmove()到Point类中。在这个例子中,x是Poin类中一个int类型的属性。

传给make()方法的源代码文本可以包含在setBody()中除了以$开头的除了$_之外的标识符。如果目标对象和目标方法名也被传给make(),那么也可以使用$proceed.例如,

CtClass point = ClassPool.getDefault().get("Point");
CtMethod m = CtNewMethod.make(
                 "public int ymove(int dy) { $proceed(0, dy); }",
                 point, "this", "move");

这段代码创建了一个ymove()方法,ymove()的定义如下:

public int ymove(int dy) { this.move(0, dy); }

注意$proceed被替换成了this.move。

Javassist提供了另一种方式来添加新的方法。你可以首先创建一个抽象的方法,然后再添加方法体:

CtClass cc = ... ;
CtMethod m = new CtMethod(CtClass.intType, "move",
                          new CtClass[] { CtClass.intType }, cc);
cc.addMethod(m);
m.setBody("{ x += $1; }");
cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT);

由于在添加抽象方法到类中时,Javassist会将类转换为抽象类,所以在setBody()之后必须明确的将类转换回非抽象类。

调用其他的方法

如果方法中调用了另一个没有被添加到类中的方法,Javassist无法编译该方法(Javassist可以编译递归调用自己的方法)。为了调用其他方法的方法到类中,你需要下面的技巧。假设你想要添加m()和n()方法到cc表示的类中:

CtClass cc = ... ;
CtMethod m = CtNewMethod.make("public abstract int m(int i);", cc);
CtMethod n = CtNewMethod.make("public abstract int n(int i);", cc);
cc.addMethod(m);
cc.addMethod(n);
m.setBody("{ return ($1 <= 0) ? 1 : (n($1 - 1) * $1); }");
n.setBody("{ return m($1); }");
cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT);

你必须先创建两个抽象方法并且添加到类中。然后你可以添加方法体到这些方法中,即使方法体中包含其他方法的调用。最后你必须将类转换回非抽象类,因为addMethod()自动的将类转换成抽象类了。

添加属性

Javassist也允许用户创建一个新的属性。

CtClass point = ClassPool.getDefault().get("Point");
CtField f = new CtField(CtClass.intType, "z", point);
point.addField(f);

这段代码添加一个z属性到Point类中。

如果添加的属性初始值必须是指定的,上面的代码应该修改如下:

CtClass point = ClassPool.getDefault().get("Point");
CtField f = new CtField(CtClass.intType, "z", point);
point.addField(f, "0");    // initial value is 0.

现在,方法addField()接收第二个参数,它是用来计算初始值的源代码文本。这段源代码文本可以是任何一个表达式,只要返回类型和属性的类型匹配。注意,一个表达式不需要以(;)结尾。

此外,上面的代码可以重写成下面这样简化的版本:

CtClass point = ClassPool.getDefault().get("Point");
CtField f = CtField.make("public int z = 0;", point);
point.addField(f);

移除成员

移除属性或方法,可以调用CtClass的removeField()或者removeMethod()。CtConstructor可以调用CtClass的removeConstructor()移除。

2. 注解

CtClass,CtMethod,CtField和CtConstructor提供了方便的方法getAnnotations()来读取注解。它返回一个注解类型对象。

例如,假设下面的注解:

public @interface Author {
    String name();
    int year();
}

这个注解使用如下:

@Author(name="Chiba", year=2005)
public class Point {
    int x, y;
}

那么,注解的值可以通过getAnnotations()获取。它返回一个包含了注解类型的对象的数组。

CtClass cc = ClassPool.getDefault().get("Point");
Object[] all = cc.getAnnotations();
Author a = (Author)all[0];
String name = a.name();
int year = a.year();
System.out.println("name: " + name + ", year: " + year);

这段代码应该大印如下:

name: Chiba, year: 2005

由于Point类的注解只有@Author,所以数组all的长度是1,all[0]代表Author对象。注解的成员值可以通过调用Author对象的name()和year()获取。

为了使用getAnnotations(),注解类型例如Author必须包含在当前的类路径。同时必须必须也可在ClassPool对象中访问。如果注解的类文件不存在,Javassist无法获取注解类型的默认值。

3. 运行时支持类

在大多数场景中,Javassist修改的类不需要Javassist去运行。然而一些由Javassist编译器生成的字节码需要运行时支持类,它被封装在javassist.runtime包中(更详细的说明,请阅读该包的相关API手册)。注意,javassist.runtime包仅仅是Javassist修改的类运行所需要的包。其它的Javassist类不需要使用。

4. 导入

所有的源代码中的类名必须是全类名(必须包含包名)。然而,java.lang包除外;例如Javassist编译器可以处理Object成java.lang.Object。

当编译器处理类名时,为了告诉编译器发现其它包,调用ClassPool的importPackage()。例如,

ClassPool pool = ClassPool.getDefault();
pool.importPackage("java.awt");
CtClass cc = pool.makeClass("Test");
CtField f = CtField.make("public Point p;", cc);
cc.addField(f);

第二行告诉编译器导入java.awt包。因此,第三行不会抛出异常。编译器可以识别Point类为java.awt.Point。

注意importPackage()对ClassPool中的get()方法中不生效。仅仅是编译器需要导入包。get()的参数必须总是全类名。

5. 限制

在当前实现中,Javassist包含的编译器在编译器可接收的语言方便有着很多限制。这些限制如下:

  • J2SE 5.0介绍的新语法(包括枚举和范型)还不被支持。注解只被Javassist的低级API支持。可以查看javassist.bytecode.annotation包。范型只支持部分。

  • 数组初始化,由{}包含的表达式中的逗号风格符不支持,除非数组是一维的。

  • 内部类或者匿名类不被支持。注意,这只是编译器的限制。它无法编译包含匿名类的声明的源代码。Javassist可以读取和修改内部类或匿名类的类文件。

  • continue和break标签语句不被支持。

  • 编译器并没有正确实现Java方法调度算法。如果类中方法的定义有同名但不同参数的方法列表,编译器可能会混淆。

    例如,

    class A {} 
    class B extends A {} 
    class C extends B {} 
    class X {
    void foo(A a) { .. }
    void foo(B b) { .. }
    }
    

    如果编译表达式是x.foo(new C()),这里的x是X的实例,编译器可能产生一个foo(A)的调用,尽管编译器可以正确的编译foo((B)new C())。

  • 推荐用户使用#作为类名和静态方法或者属性名的分隔符。例如,正常的java写法,

    javassist.CtClass.intType.getName()
    

    调用一个由javassist.CtClass的静态方法属性intType表示的对象的getName()方法。在Javassist中,用户可以像如下并推荐使用如下写法:

    javassist.CtClass#intType.getName()
    

    这样编译器可以快速解析表达式。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容