您的位置:首页 > 编程语言 > Java开发

javassist使用与源码解析(一)

2015-12-16 15:32 471 查看
Javassist是一个Java字节码操作类库,Java字节码被保存在一个被称为class文件的二进制文件中, 每个类文件都包含一个Java类或接口。

一、ClassPool

ClassPool对象是代表类文件的CtClass对象的容器。它读取类文件来构建CtClass对象,并且记录对象结构,以便于后面的访问。

程序中获取ClassPool默认如下方式:

ClassPool cp = ClassPool.getDefault();


查看ClassPool源码:

public static synchronized ClassPool getDefault() {
if (defaultPool == null) {
defaultPool = new ClassPool(null);
defaultPool.appendSystemPath();
}
return defaultPool;
}


静态方法ClassPool.getDefault()返回的默认ClassPool会搜索和当前JVM相同的搜索的路径。如果程序是运行在譬如JBoss和Tomcat之类的web应用服务器上,ClassPool对象可能就找不到用户自己的类,这是由于web应用服务器除了使用系统类加载器之外,还有多个自定义类加载器。在这种情况下,其他的类路径就需要注册到ClassPool中。例如:

cp.insertClassPath(new ClassClassPath(this.getClass()));


上面的语句将this对象对应的类路径注册进来。除了使用this.getClass(),你还可以使用任何Class对象作为参数,用于类对象的类加载路径就这样被注册进来了。

添加类路径还有以下几种方法:

//用文件目录名称作为类搜索路径
cp.appendClassPath("/com/xzq/test");
//用URL作为类搜索路径
ClassPath classPath = new URLClassPath("127.0.0.1", 8080, "/", "javassist");
cp.appendClassPath(classPath);


还有一种父子结构的ClassPool,类似classLoader
ClassPool child = new ClassPool(cp);

二、CtClass操作

2.1、获取类

CtClass是类文件的抽象代表,一个CtClass对象负责处理一个类文件,示例如下:

ClassPool cp = ClassPool.getDefault();
CtClass ct = cp.get("com.xzq.javassist.Bus");
查看ClassPool源码,看看get方法都做了哪些操作:

protected synchronized CtClass get0(String classname, boolean useCache)
throws NotFoundException {
CtClass clazz = null;
if (useCache) {
//通过缓存获取
            clazz = getCached(classname);
if (clazz != null)
return clazz;
}
//如果有父ClassPool,则从父ClassPool中获取
if (!childFirstLookup && parent != null) {
clazz = parent.get0(classname, useCache);
if (clazz != null)
return clazz;
}

clazz = createCtClass(classname, useCache);
if (clazz != null) {
// clazz.getName() != classname if classname is "[L<name>;".
if (useCache)
cacheCtClass(clazz.getName(), clazz, false);

return clazz;
}

if (childFirstLookup && parent != null)
clazz = parent.get0(classname, useCache);

return clazz;
}

protected CtClass getCached(String classname) {
//classes类型是Hashtable
        return (CtClass)classes.get(classname);
    }


从实现的角度看,ClassPool就是CtClass对象的哈希表,以类名称作为键值。ClassPool的get()方法通过指定的键值来搜寻CtClass对象。

如果当前ClassPool有父ClassPool,执行get()调用时,子ClassPool会首先委派给父ClassPool,当父ClassPool没找到这个类文件时,子ClassPool才会在它的类搜索路径下寻找这个类文件。类似ClassLoader的双亲委派原则。

当设置ClassPool.childFirstLookup=true时,子ClassPool就会先于父ClassPool来寻找此类文件。


2.2、创建类

ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.makeClass("com.xzq.javassist.Jeep");
//设置新方法的返回类型为String
CtClass ctString = new CtClass("java.lang.String") {};
ctClass.addMethod(CtNewMethod.make(ctString, "getName", null, null,"return \"123\";", ctClass));
Class c1azz = ctClass.toClass();
Method method = c1azz.getDeclaredMethod("getName", null);
System.out.println(method.invoke(c1azz.newInstance(), null));


makeClass()方法创建一个新的类,通过addMethod()方法添加一个新的方法。

makeInterface()方法创建一个新的接口,通过CtNewMethod的abstractMethod()方法创建,请注意接口方法是抽象的。 

CtClass定义了几种基本类型:

public static CtClass booleanType;
public static CtClass charType;
public static CtClass byteType;
public static CtClass shortType;
public static CtClass intType;
public static CtClass longType;
public static CtClass floatType;
public static CtClass doubleType;
public static CtClass voidType;
可以通过CtClass(String name)构造方法来设置自定义类型。

查看ClassPool源码,看看makeClass做了哪些操作:

public synchronized CtClass makeClass(String classname, CtClass superclass) throws RuntimeException{
//1、检查父ClassPool中是否存在这个类,如果存在则异常;
//2、检查类是否冻结
checkNotFrozen(classname);
CtClass clazz = new CtNewClass(classname, this, false, superclass);
//把类放入缓存
cacheCtClass(classname, clazz, true);
return clazz;
}
还有其他方式能创建类,比如通过改变类名来新建类或者重命名冻结的类来新建类,示例如下:

ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("com.xzq.javassist.Car");
//通过改变类名来新建类
cc.setName("Test");
CtClass cc1 = cp.get("com.xzq.javassist.Bus");
cc1.writeFile();
//重命名冻结的类来新建类
CtClass cc3 = cp.getAndRename("com.xzq.javassist.Bus", "NewTest");


2.3、冻结/解冻类

ClassPool cp = ClassPool.getDefault();
CtClass ct = cp.get("com.xzq.javassist.Bus");ct.writeFile();
//下面这句会报错:class is frozen
ct.setSuperclass(cp.get("com.xzq.javassist.Car"));


如果一个CtClass对象通过writeFile(),toClass(),toBytecode()方法被转换为类文件,Javassist就冻结了此对象。对此CtClass对象的后续修改都是不允许的,因为JVM不允许再次加载同一个类。

一个冻结的CtClass对象可以被解冻,这样类定义的修改就被允许。示例如下:

ClassPool cp = ClassPool.getDefault();
CtClass ct = cp.get("com.xzq.javassist.Bus");ct.writeFile();
//解冻
ct.defrost();
ct.setSuperclass(cp.get("com.xzq.javassist.Car"));
执行 defrost()方法后,CtClass对象就可再次被修改。查看源码,defrost方法操作如下:

public void defrost() {
checkPruned("defrost");
//设置冻结为false
 wasFrozen = false;
}


如果 ClassPool.doPruning()方法设置为true,Javassist可以优化调整一个被冻结的CtClass对象的数据结构。优化调整指的是为了减少内存使用,去除对象内的一些不必要的属性(比如attribute_info,方法体中的Code_attribute)。因此,当一个CtClass对象被优化调整后,一个方法的字节码除了方法名,方法签名和注解外都是不可访问的。优化后的CtClass对象不能被再次解冻。ClassPool.doPruning()方法默认值为false。调用CtClass对象的stopPruning()方法,可防止其优化调整。

2.4、删除类

ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("com.xzq.javassist.Car");
cc.writeFile();
cc.detach();


当你调用CtClass对象的 detach()方法时,CtClass对象就会从ClassPool中删除掉,不能再调用 CtClass对象的任何方法。可以通过ClassPool的get()方法获取一个新的实例,当调用get()方法时,ClassPool会再次读取class文件并创建一个新的CtClass对象。

查看detach方法源码:

public void detach() {
ClassPool cp = getClassPool();
//从缓存中删除
  CtClass obj = cp.removeCached(getName());
if (obj != this)
cp.cacheCtClass(getName(), obj, false);
}



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  javassist