JavaScript中⽂技术⽂档
1. bytecode读写
2. ClassPool
3. Class loader
4. ⾃有和定制
5. Bytecode操控接⼝
6. Generics
7. Varargs
8. J2ME
9. 装箱和拆箱
10. 调试
11. bytecode读写
Javassist是⽤来处理java字节码的类库, java字节码⼀般存放在后缀名称为class的⼆进制⽂件中。每个⼆进制⽂件都包含⼀个java类或者是java接⼝。
Javasist.CtClass是对类⽂件的抽象,处于编译中的此对象可以⽤来处理类⽂件。下⾯的代码⽤来展⽰⼀下其简单⽤法:
1: ClassPool pool = Default();
2: CtClass cc = (“test.Rectangle”);
3: cc.(“test.Point”));
4: cc.writeFile();
这段程序⾸先获取ClassPool的实例,它主要⽤来修改字节码的,⾥⾯存储着基于⼆进制⽂件构建的CtClass对象,它能够按需创建出CtClass对象并提供给后续处理流程使⽤。当需要进⾏类修改操作的时候,⽤户需要通过ClassPool实例的.get()⽅法,获取CtClass对象。从上⾯代码中我们可以看出,ClassPool的getDefault()⽅法将会查找系统默认的路径来搜索test.Rectable对象,然后将获取到的CtClass 对象赋值给cc变量。
超级演说家冠军
从易于扩展使⽤的⾓度来说,ClassPool是由装载了很多CtClass对象的HashTable组成。其中,类名为key,CtClass对象为Value,这样就可以通过搜索HashTable的Key来找到相关的CtClass对象了。如果对象没有被找到,那么get()⽅法就会创建出⼀个默认的CtClass对象,然后放⼊到HashTable中,同时将当前创建的对象返回。
从ClassPool中获取的CtClass对象,是可以被修改的。从上⾯的 代码中,我们可以看到,原先的⽗类,由test.Rectangle被改成了
test.Point。这种更改可以通过调⽤CtClass().writeFile()将其持久化到⽂件中。同时,Javassist还提供了toBytecode()⽅法来直接获取修改的字节码:
1: byte[] b = cc.toBytecode();
你可以通过如下代码直接加载CtClass:
1: Class clazz = cc.toClass();
toClass()⽅法被调⽤,将会使得当前线程中的context class loader加载此CtClass类,然后⽣成 java.lang.Class对象。更多的细节 ,请参见this ction below.
新建类
新建⼀个类,可以使⽤ClassPool.makeClass()⽅法来实现:
1: ClassPool pool = Default();
2: CtClass cc = pool.makeClass(“Point”);
上⾯的代码展⽰的是创建⽆成员⽅法的Point类,如果需要附带⽅法的话,我们可以⽤CtNewMethod附带的⼯⼚⽅法创建,然后利⽤CtClass.addMethod()将其追加就可以了 。
makeClass()不能⽤于创建新的接⼝。但是makeInterface()可以。接⼝的⽅法可以⽤CtNewmethod.abstractMethod()⽅法来创建,需要注意的是,在这⾥,⼀个接⼝⽅法其实是⼀个abstract⽅法。
冻结类
如果CtClass对象被writeFile(),toClass()或者toBytecode()转换成了类对象,Javassist将会冻结此CtClass对象。任何对此对象的后续更改都是不允许的。之所以这样做,主要是因为此类已经被JVM加载,由于JVM本⾝不⽀持类的重复加载操作,所以不允许更改。
⼀个冻结的CtClass对象,可以通过如下的代码进⾏解冻,如果想更改类的话,代码如下:
1: CtClasss cc = …;
2: :
3: cc.writeFile();
4: cc.defrost();
5: cc.tSuperclass(…); // OK since the class is not frozen.
调⽤了defrost()⽅法之后,CtClass对象就可以随意修改了。
如果ClassPool.doPruning被设置为true,那么Javassist将会把已冻结的CtClass对象中的数据结构进⾏精简,此举主要是为了防⽌过多的内存消耗。⽽精简掉的部分,都是⼀些不必要的属性(attriute_info结构)。因此,当⼀个CtClass对象被精简之后,⽅法是⽆法被访问和调⽤的,但是⽅法名称,签名,注解可以被访问。被精简过的CtClass对象可以被再次解冻。需要注意的是,ClassPool.doPruning的默认值为fal。
为了防⽌CtClass类被⽆端的精简,需要优先调⽤stopPruning()⽅法来进⾏阻⽌:
1: CtClasss cc = …;
2: cc.stopPruning(true);
3: :
4: cc.writeFile(); //转换为类⽂件,cc不会被精简.
这样,CtClass对象就不会被精简了。当writeFile()⽅法调⽤之后,我们就可以进⾏解冻,然后为所欲为了。
需要注意的是:在调试的时候, debugWriteFile()⽅法可以很⽅便的防⽌CtClass对象精简和冻住。
类搜索路径
1: pool.inrtClassPath(new Class()));
柚子怎么挑选上⾯的代码段注册了this所指向的类路径下⾯的类对象。你可以⽤其他的类对象来代替Class()。这样就可以加载其他不同的类对象了。
你也可以注册⼀个⽬录名字来作为类搜索路径。⽐如下⾯代码中,使⽤/usr/local/javalib⽬录作为搜索路径:
1: ClassPool pool = Default();
2: pool.inrtClassPath("/usr/local/javalib");
也可以使⽤url来作为搜索路径:
1: ClassPool pool = Default();
2: ClassPath cp = new URLClassPath(“”, 80, “/java/”, “org.javassist.”);
3: pool.inrtClassPath(cp);
上⾯这段代码将会添加“
1:
此外,你甚⾄可以直接使⽤⼀串字节码,然后创建出CtClass对象。⽰例如下:
梦见驴是什么意思1: ClassPool cp = Default();
2: byte[] b = a byte array;
牛肉配送3: String name = class name;
4: cp.inrtClassPath(new ByteArrayClassPath(name, b));
5: CtClass cc = cp.get(name);
从上⾯代码可以看出,ClassPool加载了ByteArrayClasPath构建的对象,然后利⽤get()⽅法并通过类名,将对象赋值给了CtClass对象。
如果你不知道类的全名,你也可以⽤makeClass()来实现:
1: ClassPool cp = Default();
2: InputStream ins = an input stream for reading a class file;
表达志向的诗句3: CtClass cc = cp.makeClass(ins);
makeClass()⽅法利⽤给定的输⼊流构建出CtClass对象。你可以⽤饿汉⽅式直接创建出ClassPool对象,这样当搜索路径中有⼤点的jar⽂件需要加载的时候,可以提升⼀些性能,之所以 这样做,原因是ClassPool对象按需加载类⽂件,所以它可能会重复搜索整个jar包中的每个类⽂件,正因为如此,makeClass()可以⽤于优化查找的性能。被makeClass()⽅法加载过的CtClass对象将会留存于ClassPool对象中,不会再进⾏读取。
⽤户可以扩展类搜索路径。可以通过定义⼀个新的类,扩展⾃ClassPath接⼝,然后返回⼀个inrtClassPath即可。这种做法可以允许其他资源被包含到搜索路径中。
2. ClassPool
⼀个ClassPool⾥⾯包含了诸多的CtClass对象。每当⼀个CtClass对象被创建的时候,都会在ClassPool中做记录。之所以这样做,是因为编译器后续的源码编译操作可能会通过此类关联的CtClass来获取。
⽐如,⼀个代表了Point类的CtClass对象,新加⼀个getter()⽅法。之后,程序将会尝试编译包含了getter()⽅法的Point类,然后将编译好的getter()⽅法体,添加到另外⼀个Line类上⾯。如果CtClass对象代表的Point类不存在的话,那么编译器就不会成功的编译getter()⽅法。需要注意的是原来的类定义中并不包含getter()⽅法 。因此,要想正确的编译此⽅法,ClassPool对象必须包含程序运⾏时候的所
有的CtClass对象。
避免内存溢出
壶口瀑布简介CtClass对象⾮常多的时候,ClassPool将会消耗内存巨⼤。为了避免个问题,你可以移除掉⼀些不需要的CtClass对象。你可以通过调⽤CtClass.detach()⽅法来实现,那样的话此CtClass对象将会从ClassPool移除。代码如下:
1: CtClass cc = … ;
2: cc.writeFile();
3: cc.detach();
此CtClass对象被移除后,不能再调⽤其任何⽅法。但是你可以调⽤()⽅法来创建⼀个新的CtClass实例。
另⼀个⽅法就是⽤新的ClassPool对象来替代旧的ClassPool对象。如果旧的ClassPool对象被垃圾回收了,那么其内部的CtClass对象也都会被垃圾回收掉。下⾯的代码可以⽤来创建⼀个新的ClassPool对象:
1: ClassPool cp = new ClassPool(true);
2: //如果需要的话,利⽤appendClassPath()来添加额外的搜索路径
上⾯的代码和Default()来创建ClassPool,效果是⼀样的。需要注意的是,Default()是⼀个单例⼯⼚⽅法,它能够创建出⼀个唯⼀的ClassPool对象并进⾏重复利⽤。new ClassPool(true)是⼀个很快捷的构造⽅法,它能够创建⼀个ClassPool对象然后追加系统搜索路径到其中。和如下的代码创建⾏为表现⼀致:
1: ClassPool cp = new ClassPool();
2: cp.appendSystemPath(); // or append another path by appendClassPath()
级联ClassPools
如果应⽤运⾏在JBOSS/Tomcat上, 那么创建多个ClassPool对象将会很有必要。因为每个类加载其都将会持有⼀个ClassPool的实例。应⽤此时最好不⽤getDefault()⽅法来创建ClassPool对象,⽽是使⽤构造来创建。
多个ClassPool对象像java.lang.ClassLoader⼀样做级联,代码如下:
2: ClassPool child = new ClassPool(parent);
3: child.inrtClassPath("./class");
如果()被调⽤,⼦ClassPool将会⾸先从⽗ClassPool进⾏查找。当⽗ClassPool查找不到后,然后将会尝试从./class⽬录进⾏查找。
如果child.childFirstLookup = true, ⼦ClassPool将会⾸先查找⾃⼰的⽬录,然后查找⽗ClassPool,代码如下:
1: ClassPool parent = Default();
2: ClassPool child = new ClassPool(parent);
3: child.appendSystemPath(); //和默认的搜索地址⼀致.
4: child.childFirstLookup = true; //修改⼦类搜索⾏为.
为新类重命名
可以从已有类创建出新的类,代码如下:
1: ClassPool pool = Default();
2: CtClass cc = (“Point”);
3: cc.tName(“Pair”);
此代码⾸先从Point类创建了CtClass对象,然后调⽤tName()重命名为Pair。之后,所有对CtClass对象的引⽤,将会由Point变成Pair。
需要注意的是tName()⽅法改变ClassPool对象中的标记。从可扩展性来看,ClassPool对象是HashTable的合集,tName()⽅法只是改变了key和Ctclass对象的关联。
因此,对于get(“Point”)⽅法之后的所有调⽤,将不会返回CtClasss对象。ClassPool对象再次读取Point.class的时候,将会创建⼀个新的CtClass,这是因为和Point关联的CtClass对象已经不存在了,请看如下代码:
1: ClassPool pool = Default();
2: CtClass cc = (“Point”);
3: CtClass cc1 = (“Point”); //cc1和cc是⼀致的.
4: cc.tName(“Pair”);
5: CtClass cc2 = (“Pair”); //cc2和cc是⼀致的.
6: CtClass cc3 = (“Point”); //cc3和cc是不⼀致的.
cc1和cc2将会指向cc,但是cc3却不会。需要注意的是,在cc.tName(“Pair”)执⾏后,cc和cc1指向的CtClass对象都变成了指向Pair类。
ClassPool对象⽤来维护类之间和CtClass对象之间⼀对⼀的映射关系。Javassist不允许两个不同的CtClass对象指向同⼀个类,除⾮两个独⽴的ClassPool存在的情况下。这是为实现程序转换⽽保证其⼀致性的最鲜明的特点。
我们知道,可以利⽤Default()⽅法创建ClassPool的实例,代码⽚段如下(之前已经展⽰过):
1: ClassPool cp = new ClassPool(true);
如果你有两个ClassPool对象,那么你可以从这两个对象中分别取出具有相同类⽂件,但是⾪属于不同的CtClass对象⽣成的,此时可以通过修改这俩CtClass对象来⽣成不同的类。
从冻结类中创建新类
当CtClass对象通过writeFile()⽅法或者toBytecode()转变成类⽂件的时候,Javassist将不允许对这个CtClass对象有任何修改。因此,当代表Point类的CtClass对象被转换成了类⽂件,你不能够先拷贝Point类,然后修改名称为Pair类,因为Point类中的tName()⽅法是⽆法被执⾏的,错误使⽤⽰例如下:
1: ClassPool pool = Default();
2: CtClass cc = (“Point”);
3: cc.writeFile();
4: cc.tName(“Pair”); // wrong since writeFile() has been called.
为了能够避免这种限制,你应该使⽤getAndRename()⽅法,正确⽰例如下:
2: CtClass cc = (“Point”);
3: cc.writeFile();
4: CtClass cc2 = AndRename(“Point”, “Pair”);
如果getAndRename()⽅法被调⽤,那么ClassPool⾸先会基于Point.class来创建⼀个新的CtClass对象。之后,在CtClass对象被放到HashTable前,它将CtClass对象名称从Point修改为Pair。因此,getAndRename()⽅法可以在writeFile()⽅法或者toBytecode()⽅法执⾏后去修改CtClass对象。
3. 类加载器
如果预先知道需要修改什么类,最简单的修改⽅式如下:
如何煮鸡蛋1. 调⽤()⽅法获取CtClass对象
2. 修改此对象
3. 调⽤CtClass对象的writeFile()⽅法或者toBytecode()⽅法来⽣成类⽂件。
如果检测类是否修改⾏为发⽣在程序加载的时候,那么对于⽤户说来,Javassist最好提供这种与之匹配的类加载检测⾏为。事实
上,javassist可以做到在类加载的时候来修改⼆进制数据。使⽤Javassist的⽤户可以定义⾃⼰的类加载器,当然也可以采⽤Javassist⾃⾝提供的。
3.1 CtClass中的toClass⽅法
CtClass提供的toClass()⽅法,可以很⽅便的加载当前线程中通过CtClass对象创建的类。但是为了使⽤此⽅法,调⽤⽅必须拥有⾜够的权限才⾏,否则将会报SecurityException错误。
下⾯的代码段展⽰了如何使⽤toClass()⽅法:
1: public class Hello {
2: public void say() {
3: System.out.println(“Hello”);
4: }
5: }
6:
7: public class Test {
8: public static void main(String[] args) throws Exception {
9: ClassPool cp = Default();
10: CtClass cc = cp.get(“Hello”);
11: CtMethod m = cc.getDeclaredMethod(“say”);快递多久停
12: m.inrtBefore("{ System.out.println(“Hello.say():”); }");
13: Class c = cc.toClass();
14: Hello h = (wInstance();
15: h.say();
16: }
17: }
Test.main()⽅法中, say()⽅法被插⼊了println()⽅法,之后这个被修改的Hello类实例被创建,say()⽅法被调⽤。
需要注意的是,上⾯代码中,Hello类是放在toClass()之后被调⽤的,如果不这么做的话,JVM将会先加载Hello类,⽽不是在toClass()⽅法加载Hello类之后再调⽤Hello类,这样做会导致加载失败(会抛出LinkageError错误)。⽐如,如果Test.main()⽅法中的代码如下: