1. Introduction

A few days ago, there is a big guy in the Q group, showing the next Android do no trace buried point, feel very powerful asked the use of AspectJ, online search information ASM than AspectJ is more flexible, more lightweight just take advantage of the May Day holiday system learningCopy the code

2. Introduction

ASM is a lightweight Repository for Java bytecode manipulationCopy the code

3. Prepare

3.1 Simple ASM knowledge

There are several classes that ASM needs to know and you need to be familiar with the Java bytecode reading and analysis engine for ClassReader bytecode. It uses a sas-like event reading mechanism, and calls the registered ClassVisitor, AnnotationVisitor, FieldVisitor, MethodVisitor to do the corresponding processing whenever an event occurs. ClassVisitor defines events that are triggered when Class bytecode is read, such as Class header parsing completion, annotation parsing, field parsing, method parsing, etc. For example, annotations that parse to a base value type, annotations of enum value type, annotations of Array value type, annotations of annotation value type, etc. FieldVisitor defines the event that is triggered when a field is parsed, MethodVisitor defines the events that are triggered when a method is parsed, such as annotations on a method, properties, code on a method, and so on. ClassWriter It implements the ClassVisitor interface for concatenating bytecode.Copy the code

3.2 Preparing Development Tools

Idea/Android Studio ASM Bytecode ViewerCopy the code

4 real

4.1 Effect to be achieved

class Hello {
    public static void main(String[] args) {
        show();
    }

    public static void show(a){
        System.out.println("Hello World"); }}Copy the code
Above is a Hello class that computes and prints the elapsed time of the show() methodCopy the code

4.2 Writing ASM Logic

2 write ClassVisitor

A listener that parses a Class and fires internal methods when the Class bytecode is parsedCopy the code
public class TestClassVisitor extends ClassVisitor {
    public TestClassVisitor(final ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        if(cv ! =null) { cv.visit(version, access, name, signature, superName, interfaces); }}@Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        If methodName is show, return our custom TestMethodVisitor
        if ("show".equals(name)) {
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            return new TestMethodVisitor(mv);
        }
        if(cv ! =null) {
            return cv.visitMethod(access, name, desc, signature, exceptions);
        }
        return null; }}Copy the code

4.2.2 write MethodVisitor

Oracle ASM Bytecode Viewer is an oracle ASM Bytecode Viewer. The ASM Bytecode Viewer plugin is recommended!! The ASM Bytecode Viewer plugin is recommended!! Create a new class to write the code to inject, and then use the plug-in to view itCopy the code

public class TestMethodVisitor extends MethodVisitor implements Opcodes {
    public TestMethodVisitor(MethodVisitor mv) {
        super(Opcodes.ASM5, mv);
    }

    @Override
    public void visitCode(a) {
        // called when the method body starts
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System"."currentTimeMillis"."()J".false);
        mv.visitVarInsn(LSTORE, 0);
        super.visitCode();
    }
    @Override
    public void visitInsn(int opcode) {
        // this is called every time a command is executed
        if (opcode == Opcodes.RETURN) {
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System"."currentTimeMillis"."()J".false);
            mv.visitVarInsn(LLOAD, 0);
            mv.visitInsn(LSUB);
            mv.visitVarInsn(LSTORE, 2);
            Label l3 = new Label();
            mv.visitLabel(l3);
            mv.visitLineNumber(11, l3);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System"."out"."Ljava/io/PrintStream;");
            mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
            mv.visitInsn(DUP);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder"."<init>"."()V".false);
            mv.visitLdcInsn("== method cost time = ");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder"."append"."(Ljava/lang/String;) Ljava/lang/StringBuilder;".false);
            mv.visitVarInsn(LLOAD, 2);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder"."append"."(J)Ljava/lang/StringBuilder;".false);
            mv.visitLdcInsn(" ==");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder"."append"."(Ljava/lang/String;) Ljava/lang/StringBuilder;".false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder"."toString"."()Ljava/lang/String;".false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream"."println"."(Ljava/lang/String;) V".false);

        }
        super.visitInsn(opcode); }}Copy the code

4.3 Test Effect

Write test classes to run

public class Demo {
    public static void main(String[] args) throws IOException {
        ClassReader cr = new ClassReader(Hello.class.getName());
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new TestClassVisitor(cw);
        cr.accept(cv, Opcodes.ASM5);
        // Get the binary stream corresponding to the generated class file
        byte[] code = cw.toByteArray();
        // Write the binary stream to out/
        FileOutputStream fos = new FileOutputStream("out/Hello.class"); fos.write(code); fos.close(); }}Copy the code

The.class file generated by the original Hello class, and the output

The.class file and output of the Hello class after the bytecode modification

use

It can be used for traceless embedding, log printing, and performance monitoringCopy the code

TIPS:

Github