说起 AOP 小伙伴们肯定很熟悉,无论是 JDK 动态代理或者是 CGLIB 等,其底层都是通过操作 Java 字节码来实现代理。ASM、AspectJ和Javassist等是常用的操作字节码的技术。
ASM 其设计和实现是尽可能小而且快,更专注于性能。它在指令的层面来操作,所以使用它需要对 JVM 的指令有所了解,门槛较高,CGLIB 就使用了 ASM 技术。
AspectJ 扩展了 Java 语言,定义了一系列 AOP 语法,在 JVM 中运行需要使用特定的编译器生成遵守 Java 字节码规范的 Class 文件,Spring AOP 使用了 AspectJ 。
Javassist 直接使用 Java 编码的形式操作字节码,简单易上手,性能高于反射,相比于 ASM 稍低。
Javassist 抽象出一个 ClassPool 对象来操作 Java 类,可以通过 ClassPool.getDefault() 来获取默认的 ClassPool 。常用的对象:
CtClass:代表一个 Class 的实例,可以通过类的全限定名来获取 CtClass 对象,其中包含了对 Class 的各种操作。
ClassPool:通过 HashTable 保存了路径下的 CtClass 信息,key为类的全限定名称,value 为类名对应的 CtClass 对象。
CtMethod、CtField:抽象出类的方法和属性,可以用于定义或修改方法和字段。
<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();
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怎么使用的详细内容。更多信息请关注PHP中文网其他相关文章!