java+字节码工具64位_javassist:字节码编辑器工具

简介:

javassist是一款可以在运行时生成字节码的工具,可以通过它来构造一个新的class对象、method对象,这个class是运行时生成的。可以通过简短的几行代码就可以生成一个新的class type

ClassPool pool =ClassPool.getDefault();

CtClass cc= pool.get("test.Rectangle");

cc.setSuperclass(pool.get("test.Point"));

//CtClass cc = pool.makeClass("Point");

cc.writeFile("path");

上面的代码就会把新生成的class 文件写到文件系统中,javassist封装了从 方法字符串 到 字节码的逻辑,用户可以方便的像写程序一样,生成一个新类。

运行时动态生成class,有点类似cglib,和cglib不同的是,javassist可以直接编辑类里面的属性、方法源码,而cglib没有封装这些接口。

举例说明,在实现一个aop功能时,javassist可以通过重新写method的源码来实现,而cglib需要实现类型MethodInvoker这种接口。

javassist可以修改method的源代码,执行这些代码不需要反射,就像执行提前编写好的硬代码一样,而cglib的callback是反射实现的。当然,他们的效率不会有太大差异,各种缓存策略也保证了他们的执行效率;

下面是一些基础知识

If a CtClass object is converted into a class file by writeFile(), toClass(), or toBytecode(), Javassist freezes that CtClass object. Further modifications

of that CtClass object are not permitted

A frozen CtClass can be defrost so that modifications of the class definition will be permitted

CtClasss cc = ...;

:

cc.writeFile();

cc.defrost();

cc.setSuperclass(...); // OK since the class is not frozen.

After defrost() is called, the CtClass object can be modified again.

如果想让CtClass不可修改,可以使用stopPruning

CtClasss cc = ...;

cc.stopPruning(true); //不能修剪

:

cc.writeFile(); // convert to a class file.

// cc is not pruned.

一般来说运行时再修改CtClass风险太大,不建议修改它,尤其是执行toClass方法后

Class clz = cc.toClass();

T instance = (T) clz.newInstance();

由于ClassPool.getDefault() 搜索class时用的classpath和当前的是JVM级的class path是相同的,同时它的classLoader是当前线程上下文的classloader,也就是App classloader,因此如果一个应用使用tomcat启动时,可能无法找到当前webapp对应的用户的classpath(一个tomcat占用一个jvm,而一个tomcat可以运行多个webapp,每个webapp都是不同的class loader、classpath),也就无法搜索到对应的class。

可以使用

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

来插入classpath,这样,不同的webapp 搜索class 时,也就能找到自己的class,而不会交叉。

规避内存溢出

如果ClassPool中有非常多的CtClass ,有可能会导致内存溢出,因此提供了一个方法,可以把不用的CtClass 删掉

CtClass cc = ... ;

cc.writeFile();

cc.detach();

调用detach()方法后,就不能再操作CtClass对象了,但是可以通过 pool.get("test.Rectangle") 来重新加载该对象

或者重新创建一个ClassPool(按照文档的意思,是里面的CtClass你也丢弃了),没有引用链的老ClassPool就被垃圾回收了,包括里面的CtClass

ClassPool cp = new ClassPool(true);

// if needed, append an extra search path by appendClassPath()

//因为 new ClassPool(true)相当于ClassPool cp = new ClassPool();cp.appendSystemPath(); // or append another path by appendClassPath()

个人理解在tomcat启动的应用,构建ClassPool有两种方式

1:

ClassPool parent = ClassPool.getDefault();

ClassPool pool = new ClassPool(parent);

//tomcat下启动,不同的webapp有不同的classpath

pool.insertClassPath(new ClassClassPath(ProxyFactory.class));

2:

ClassPool pool = new ClassPool(false);

//tomcat下启动,不同的webapp有不同的classpath

pool.insertClassPath(new ClassClassPath(ProxyFactory.class));

区别就是第2中没有parent classloader,全部的CtClass都在自己的ClassPool对象中,而1中,有些CtClass可能被放到parent中

下面的ClassPool对象,通过get方法获取CtClass的源码

/**

* @param useCache false if the cached CtClass must be ignored.

* @return null if the class could not be found.

*/

protected synchronized CtClass get0(String classname, boolean useCache)

throws NotFoundException

{

CtClass clazz = null;

if (useCache) {

clazz = getCached(classname);

if (clazz != null)

return clazz;

}

if (!childFirstLookup && parent != null) { //childFirstLookup默认值为false

clazz = parent.get0(classname, useCache);

if (clazz != null)

return clazz;

}

clazz = createCtClass(classname, useCache);

if (clazz != null) {

// clazz.getName() != classname if classname is "[L;".

if (useCache)

cacheCtClass(clazz.getName(), clazz, false);

return clazz;

}

if (childFirstLookup && parent != null)

clazz = parent.get0(classname, useCache);

return clazz;

}

Classloader部分:待续

顺便一提tomcat中启动应用的classloader结构,下图中从下到上的关系中,上为parent。

Bootstrap classloader 是最底层的ClassLoader,它没有parent,加载的是java最核心的类和包,相传它是C++直接写的;

ExtClassLoader加载的是扩展包%JAVA_HOME%/jre/lib/ext目录下的一些包,它的parent是null,表示它是仅次于Bootstrap ClassLoader,也属于最底层的ClassLoader;

AppClassLoader就是我们运行一个普通的java 程序时,我们自己写的类会使用AppClassLoader来加载;

基于tomcat容器,tomcat会在AppClassLoader上创建子ClassLoader StandardClassLoader,而此时的AppClassLoader仅会加载tomcat的bootstrap.jar和juli.jar,StandardClassLoader则会加载更多的tomcat的lib包,作为一个基础的ClassLoader,这么做的用意是防止tomcat的lib包影响到tomcat的正常启动;

WebAppClassLoader是扔进tomcat中的引用代码(即我们自己的类)的类加载器,它的parent是StandardClassLoader;

f13d43f0fc3e9a8ae511a8c33ffeb2ed.png

同时,WebAppClassLoader加载的类,它的线程上下文ClassLoader也会被设置成WebAppClassLoader,如果一个tomcat中有两个应用,很显然,他们是两个类加载器,因此,javassist文档中的注意事项也是可以忽略的,原因是ClassPool.getDefault()方法会使用线程上下文ClassLoader,因此它会找到一个正确的ClassLoader,也就对应了一个正确的classpath

内省和定制

内省这里简单理解成调用属性域的get,set方法,在javassist中,通过java 反射api实现,比如你通过定制在CtClass中新添了一个属性,然后你可以调用set方法来给该属性赋值

下面来说说定制,javassist中,method对象的原型是CtMethod

CtMethod ctMethod = CtNewMethod.make(sb.toString(), cc);

上面的代码是增加一个新的方法,sb.toString()代表的是这个方法的字符串,如“public int geti(){return i;}”,它是一个完整的方法体

同时ctMethod有多个方法可以操作

获取CtMethod 对象后,还可以再操作这个方法,如insertBefore(),insertAfter()等等

ClassPool pool = ClassPool.getDefault();

CtClass cc = pool.get("Point");

CtMethod m = cc.getDeclaredMethod("move");

m.insertBefore("{ System.out.println($1); System.out.println($2); }");

cc.writeFile();

标识符,它们都以$开头,适用于编写方法,获取或操作方法的参数

$0, $1, $2, ... $0表示this,$n,...获取第n个参数

$argsAn array of parameters. The type of $args is Object[].

int 会被转换成Integer,再用$args[0]时,会转回int

$$All actual parameters.

For example, m($$) is equivalent to m($1,$2,...)

$cflow(...)cflow variable 返回递归调用成层数,0表示调用一次

$rThe result type. It is used in a cast expression. 与$w相反

$wThe wrapper type. It is used in a cast expression. ($w)$1可以把int转成Integer

$_The resulting value

$sigAn array of java.lang.Class objects representing the formal parameter types.

$typeA java.lang.Class object representing the formal result type.

$classA java.lang.Class object representing the class currently edited.

在编码过程中,我遇到过int 直接放入Object[]时,VefifyTypeError,即Object[]中只能放封装类型,如Integer,而不能放原始类型int,而平常我们写代码试,把int放入Object[]时没有报错的原因是java编译器在编译成class文件时,已经将int做了转换。而目前的javassist还没有这么智能,但是预留了$w来处理这个问题。

关于各种标识符,可以参考官方文档:http://jboss-javassist.github.io/javassist/tutorial/tutorial2.html

使用javassist的代码:

https://github.com/jianliu/lsf/blob/master/src/main/java/com/liuj/lsf/client/ProxyFactory.java

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值