javassist使用与源码解析(一)

来源:互联网 发布:淘宝美工教程视频 编辑:程序博客网 时间:2024/06/08 07:32

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");//设置新方法的返回类型为StringCtClass 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 frozenct.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 0
原创粉丝点击