asm 其设计和实现是尽可能小而且快,更专注于性能。它在指令的层面来操作,所以使用它需要对 jvm 的指令有所了解,门槛较高,cglib 就使用了 asm 技术。
aspectj 扩展了 java 语言,定义了一系列 aop 语法,在 jvm 中运行需要使用特定的编译器生成遵守 java 字节码规范的 class 文件,spring aop 使用了 aspectj 。
javassist 直接使用 java 编码的形式操作字节码,简单易上手,性能高于反射,相比于 asm 稍低。
javassist 常用类javassist 抽象出一个 classpool 对象来操作 java 类,可以通过 classpool.getdefault() 来获取默认的 classpool 。常用的对象:
ctclass:代表一个 class 的实例,可以通过类的全限定名来获取 ctclass 对象,其中包含了对 class 的各种操作。
classpool:通过 hashtable 保存了路径下的 ctclass 信息,key为类的全限定名称,value 为类名对应的 ctclass 对象。
ctmethod、ctfield:抽象出类的方法和属性,可以用于定义或修改方法和字段。
javassist 的使用依赖<dependency> <groupid>org.javassist</groupid> <artifactid>javassist</artifactid> <version>3.27.0-ga</version></dependency>
代码示例// 获取默认类池 classpool classpool = classpool.getdefault(); // 1. 创建空类 ctclass ctclass = classpool.makeclass("com.aysaml.demo.javassist.user"); // 2. 创建 string 类型的 name 字段 ctfield field = new ctfield(classpool.get("java.lang.string"), "name", ctclass); // 设置字段访问级别 private field.setmodifiers(modifier.private); // 增加字段 ctclass.addfield(field); // 3. 增加 getter & setter 方法 ctclass.addmethod(ctnewmethod.getter("getname", field)); ctclass.addmethod(ctnewmethod.setter("setname", field)); // 4. 增加无参构造方法:其中 $0 表示 this,$1 表示参数 ctconstructor noargscons = new ctconstructor(new ctclass[] {}, ctclass); noargscons.setbody("{$0.name=\"mark\";}"); ctclass.addconstructor(noargscons); // 5. 增加有参构造方法 ctconstructor hasargscons = new ctconstructor(new ctclass[] {classpool.get("java.lang.string")}, ctclass); hasargscons.setbody("{$0.name=$1;}"); ctclass.addconstructor(hasargscons); // 6. 创建方法 ctmethod method = new ctmethod(ctclass.voidtype, "printname", new ctclass[] {}, ctclass); method.setbody("{system.out.println($0.name);}"); ctclass.addmethod(method); // 7. 生成类文件:可指定路径,默认为当前项目根目录 ctclass.writefile(); // 8. 创建类实例 object person = ctclass.toclass().newinstance();
如何实现类似 aop 的功能javassist 对于编程化的操作字节码是很简单易懂的,我们以在方法的开头结尾打印信息为例:
public class cat { /** 记录喵喵喵的次数 */ private int num; public void miao() { this.num++; }}
我们要在 miao( ) 方法的前增加声音输出:
public static void main(string[] args) throws notfoundexception, cannotcompileexception { classpool classpool = classpool.getdefault(); // 获取 cat 类的 ctclass 对象 ctclass catclass = classpool.get("com.aysaml.demo.javassist.cat"); // 获取 miao( ) 方法 ctmethod method = catclass.getdeclaredmethod("miao"); method.insertbefore("system.out.println(\"miao~\");"); // 加载修改过的类,注意必须要保证调用前这个类没有被加载过 catclass.toclass(); //测试 cat cat = new cat(); cat.miao(); }
注意到,在使用 catclass.toclass() 加载被修改过的类时,强调必须保证在调用前这个类没有被加载过,否则会报 attempted duplicate class definition for name 异常。
我们知道一个类是不能被一个类加载器加载两次的,所以为了解决这个问题,需要制定一个没有加载过该类的 classloader,javassist 提供了一个 classloader ,如下:
public class cat { /** 记录喵喵喵的次数 */ private int num; public void miao() { system.out.println("调用了 miao 方法"); this.num++; } public static void main(string[] args) throws exception{ classpool classpool = classpool.getdefault(); // 获取 cat 类的 ctclass 对象 ctclass catclass = classpool.get("com.aysaml.demo.javassist.cat"); // 获取 miao( ) 方法 ctmethod method = catclass.getdeclaredmethod("miao"); method.insertbefore("system.out.println(\"miao~\");"); // 重新设置一个 classloader loader classloader = new loader(classpool); class clazz = classloader.loadclass("com.aysaml.demo.javassist.cat"); // 调用修改过的类的方法 clazz.getdeclaredmethod("miao").invoke(clazz.newinstance()); }}
执行结果为:
以上就是java中的javassist怎么使用的详细内容。
