javassist使用与源码解析(一)

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相同的搜索的路径。如果程序是运行在譬如JBossTomcat之类的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对象的哈希表,以类名称作为键值。ClassPoolget()方法通过指定的键值来搜寻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()方法设置为trueJavassist可以优化调整一个被冻结的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对象的任何方法。可以通过ClassPoolget()方法获取一个新的实例,当调用get()方法时,ClassPool会再次读取class文件并创建一个新的CtClass对象。

查看detach方法源码:

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






  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值