欢迎关注微信公众号:互联网全栈架构
Javaassist(JAVA programming ASSISTant),是一个可以操控字节码的类库,能够在不修改源代码的情况下,在运行时动态地对类的结构和方法进行拓展(当然也可以新增类)。它提供了丰富的API,让开发人员可以方便地对字节码进行操控。下面我们用实例来展示它的使用。创作不易,辛苦在文末点个赞,谢谢!
首先初步认识一下字节码,我们知道,Java源代码在编译后生成了class文件,也就是字节码文件,JVM加载class文件后再翻译成对应平台的指令。定义一下简单的类,看看生成的字节码文件是什么样子,有一个初步的直观感受:
package com.sample.core.bytecode;
public class Calculator {
public int sum(int x, int y){
System.out.println("Sum Operation");
return x+y;
}
}
编译后生成了.class文件,使用javap命令查看文件的字节码:
javap -c Calculator.class
public class com.sample.core.bytecode.Calculator {
public com.sample.core.bytecode.Calculator();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int sum(int, int);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Sum Operation
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: iload_1
9: iload_2
10: iadd
11: ireturn
}
以上就是对应的字节码,而Javaassist提供了丰富的API,可以操控字节码文件。对于这些字节码指令的具体含义,可以参考具体的资料说明,这里不做说明。我们来看看Javaassist的一些主要功能,比如新增一个类、加载已有类并修改、新增字段和方法等。首先在POM文件中引入依赖:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
一、生成一个新类
Javaassist可以新生成一个类,比如我们想新生成一个类ManuNewClass,它实现接口java.io.Serializable,并且有一个名为id的成员变量,ClassFile用于定义一个新类,FieldInfo用于新增一个成员变量,CtClass是类的抽象表示形式:
// 新增一个类,并实现接口
ClassFile cf = new ClassFile(
false, "com.sample.core.bytecode.ManuNewClass", null);
cf.setInterfaces(new String[] {"java.io.Serializable"});
// 增加新的成员变量
FieldInfo f = new FieldInfo(cf.getConstPool(), "id", "id");
f.setAccessFlags(AccessFlag.PUBLIC);
cf.addField(f);
// 生成新类
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass(cf);
二、修改现有类
比如我们之前定义的类Calculator,现在修改它的父类为com.sample.core.bytecode.ParentClass:
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get("com.sample.core.bytecode.Calculator");
ctClass.setSuperclass(classPool.get("com.sample.core.bytecode.ParentClass"));
三、新增成员变量
在之前定义的计算器类中新增成员变量id:
ClassPool classPool = ClassPool.getDefault();
ClassFile classFile = classPool.get("com.sample.core.bytecode.Calculator").getClassFile();
FieldInfo fieldInfo = new FieldInfo(classFile.getConstPool(), "id", "id");
fieldInfo.setAccessFlags(AccessFlag.PUBLIC);
classFile.addField(fieldInfo);
四、新增成员方法
比如我们要新增一个test方法,它打印Hello World:
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get("com.sample.core.bytecode.Calculator");
CtMethod ctMethod =CtNewMethod.make("public void test() { System.out.println(\"Hello World\");}", ctClass);
ctClass.addMethod(ctMethod);
五、示例
我们以常见的AOP为例,比如我们要在方法调用之前做一些操作,在方法调用之后再做别的操作,类似于这样的功能,用Javaassist是可以实现的,对于我们在文章开头定义的计算器类,如果我们需要在计算之前打印信息,在计算完成后也要给出提示,在不修改源代码的情况下,实现方法如下:
package com.sample.core.bytecode;
import javassist.*;
public class JavaassistExample {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass targetClass = pool.get("com.sample.core.bytecode.Calculator");
CtMethod targetMethod = targetClass.getDeclaredMethod("sum");
// 在方法调用前打印信息
targetMethod.insertBefore("{ System.out.println(\"Start Calculating:\"); }");
// 在方法调用后打印信息
targetMethod.insertAfter("{ System.out.println(\"End Calculating:\"); }");
Class c = targetClass.toClass();
Calculator calculator = (Calculator) c.newInstance();
calculator.sum(5, 7);
}
}
运行上面的程序,在控制台中打印出如下信息,可以看出,在调用方法sum前后,都打印出了对应的信息:
Start Calculating:
Sum Operation
End Calculating:
六、总结
Javaassist可以对字节码进行操作,所以对于AOP、动态代理、安全检测、链路跟踪等方面都能够广泛应用,相比于ASM,它提供了更为友好和便捷的API,但在性能要求较为苛刻的场合,ASM更胜一筹。
以上就是关于字节码增强的类库Javaassist介绍,谢谢!
都看到这里了,请帮忙一键三连啊,也就是点击文末的在看、点赞、分享,这样会让我的文章让更多人看到,也会大大地激励我进行更多的输出,谢谢!
鸣谢:
https://github.com/jboss-javassist/javassist
https://www.51cto.com/article/750294.html
推荐阅读: