Author: Dong Zilong
Preface
In the recent period, I have been thinking of writing a patent for a plugin that can generate business document modification records based on the implementation principle of lombok. During the process of reviewing materials, I accidentally learned about the bytecode enhancement tool - byteBuddy. However, due to the tight schedule at that time, I did not delve into the understanding of this component. In fact, the身影 of bytecode enhancement components is everywhere in our daily development, such as spring-aop and mybatis. Adhering to the spirit of knowing not only the what but also the why, I decided to sink my heart into a thorough study and summary of bytecode enhancement technology. This article serves as the opening of this series, mainly for a simple introduction to bytecode, laying a good foundation for our subsequent in-depth study.
1. Brief introduction to bytecode

Bytecode is a binary file in an intermediate state, which is compiled from source code and has lower readability than source code. The CPU cannot directly read bytecode, and in Java, bytecode needs to be translated into machine code by JVM before the CPU can read and run it.
The benefits of using bytecode: compile once, run anywhere. Java is a typical example of using bytecode as an intermediate language, where the source code is compiled once, and the .class file can be run on various computers.
2. Use scenarios of bytecode enhancement
If we do not want to modify the source code but want to add new features and make the program run as expected, we can do the corresponding operations during the compilation process and loading process. Simply put, it is: modify or replace the generated .class file with the target .class file we need.
Since bytecode enhancement can implant code logic without侵入 business code at all, it can be used to do some cool things, such as the following common scenarios:
1. Dynamic proxy
2. Hot deployment
3. Call chain tracking埋点
4. Dynamic insertion of log (performance monitoring)
5. Test code coverage tracking
...
3. Implementation methods of bytecode enhancement
Bytecode tools | Class creation | Implement interface | Method call | Class extension | Parent class method call | Advantages | Disadvantages | Common usage | Learning cost |
---|---|---|---|---|---|---|---|---|---|
java-proxy | Supports | Supports | Supports | Does not support | Does not support | Preferred for simple dynamic proxy | Limited in function, does not support expansion | spring-aop, MyBatis | 1 star |
asm | Supports | Supports | Supports | Supports | Supports | Can insert arbitrary bytecode, almost unrestricted | High learning difficulty, more code writing | cglib | 5 stars |
javaassit | Supports | Supports | Supports | Supports | Supports | Java original syntax, string insertion in string form, writing is intuitive | Does not support syntax above JDK1.5, such as generics, enhanced for | Fastjson, MyBatis | 2 stars |
cglib | Supports | Supports | Supports | Supports | Supports | Looks similar to bytebuddy | Is being phased out by bytebuddy | EasyMock, jackson-databind | 3 stars |
bytebuddy | Supports | Supports | Supports | Supports | Supports | Supports interception of any dimension, can obtain the original class, method, as well as proxy class and all parameters | Not very intuitive, with some cost in learning and understanding, with a very large number of APIs | SkyWalking, Mockito, Hibernate, powermock | 3 stars |
4. Simple Example
AOP is an architectural design idea commonly used in our daily development, AOP'sThe main implementations include cglib, Aspectj, Javassist, java proxy, and others. Next, we will take the logging before and after method execution, which we often encounter in our daily development, as a starting point to manually implement AOP using bytecode.
Define the target interface and implementation
public class SayService{
public void say(String str) {
System.out.println("hello" + str);
}
}
Define the class SayService, before executing the say method, we will printWhen the method starts to execute start, after the method is executed, we will print the end of method execution
ASM implements AOP
4.1.1, Introduction of jar package
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.1</version>
</dependency>
4.1.2, Specific implementation of AOP
public class ResourceClassVisitor extends ClassVisitor implements Opcodes {
public ResourceClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM4, cv);
}
public ResourceClassVisitor(int i, ClassVisitor classVisitor) {
super(i, classVisitor);
}
/**Access class basic information*/
@Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
this.cv.visit(version, access, name, signature, superName, interfaces);
}
/**Access method basic information*/
@Override
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
MethodVisitor mv = this.cv.visitMethod(access, name, desc,
signature, exceptions);
//假如不是构造方法,我们构建方法的访问对象(MethodVisitor)
if (!name.equals("<init>") && mv != null) {
mv = new ResourceClassVisitor.MyMethodVisitor((MethodVisitor)mv);
}
return (MethodVisitor)mv;
}
/**自定义方法访问对象*/
class MyMethodVisitor extends MethodVisitor implements Opcodes {
public MyMethodVisitor(MethodVisitor mv) {
super(Opcodes.ASM4, mv);
}
/**此方法会在方法执行之前执行*/
@Override
public void visitCode() {
super.visitCode();
this.mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
this.mv.visitLdcInsn("方法开始执行start");
this.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
"println", "(Ljava/lang/String;)V", false);
}
/**对应方法体本身*/
@Override
public void visitInsn(int opcode) {
//在方法return或异常之前,添加一个end输出
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
this.mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
this.mv.visitLdcInsn("方法执行结束end");
this.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
"println", "(Ljava/lang/String;)V", false);
}
this.mv.visitInsn(opcode);
}
}
}
public class AopTest {
public static void main(String[] args) throws IOException {
//First step: Build the ClassReader object to read the specified class file (default is classpath)
ClassReader classReader = new ClassReader("com/aop/SayService");
//Second step: Build the ClassWriter object, based on which a new class file is created
//ClassWriter.COMPUTE_FRAMES indicates that ASM will automatically calculate max stacks, max locals, and the specific content of stack map frames.
//ClassWriter.COMPUTE_MAXS indicates that ASM will automatically calculate max stacks and max locals, but will not automatically calculate stack map frames.
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); //Recommended to use COMPUTE_FRAMES
//Third step: Build the ClassVisitor object, which is used to receive data from the ClassReader objectand pass the processed data to the ClassWriter object
ClassVisitor classVisitor = new ResourceClassVisitor(classWriter);
//Fourth step: Read class information based on ClassReader and pass the data to the ClassVisitor object
//The parameter ClassReader.SKIP_DEBUG here indicates skipping some debugging information, etc., making the ASM code look simpler
//The parameter ClassReader.SKIP_FRAMES here indicates skipping some part of stack frame information in certain methods, as manual calculation of stack frames is very complex, so let the system handle it
//Recommended parameters
classReader.accept(classVisitor, ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
//Fifth step: Obtain data from ClassWriter and write it to a class file
byte[] data = classWriter.toByteArray();
//Write bytecode into the disk's class file
File f = new File("target/classes/com/aop/SayService.class");
FileOutputStream fout = new FileOutputStream(f);
fout.write(data);
fout.close();
SayService rs = new SayService();
rs.say("asm");//start,handle(),end
}
}
4.1.3 Test class output results
Javassist implements AOP
4.2.1 Introduction of jar package
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
4.2.2 Specific implementation of AOP
public class AopTest {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("com.aop.SayService");
CtMethod personFly = cc.getDeclaredMethod("say");
personFly.insertBefore("System.out.println("\"Method execution starts start\")");
personFly.insertAfter("System.out.println("\"Method execution ends end\")");
cc.toClass();
SayService sayService = new SayService();
sayService.say("assist");
}
}
4.2.3 Test class output results
5. Summary
As the opening article of the series on bytecode enhancement, it simply introduces the definition and implementation methods of bytecode, and finally demonstrates how to enhance bytecode through specific examples. In subsequent articles, a detailed summary of the principles and specific applications of related frameworks will be provided, and I welcome all experts to criticize and correct me.

评论已关闭