ASM插桩——动态添加字段并生成get set 方法

1 首先创建一个实体类Student. 代码如下

java 复制代码
package com.org.xcyz.asm;
 
public class Student {
    private int id;
    private String name;
    private boolean sex;
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public boolean isSex() {
        return sex;
    }
 
    public void setSex(boolean sex) {
        this.sex = sex;
    }
}

2 然后准备添加字段age和setAge以及getAge方法

java 复制代码
package com.org.xcyz.asm.trans;
 
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
 
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
 
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.IRETURN;
import static org.objectweb.asm.Opcodes.PUTFIELD;
import static org.objectweb.asm.Opcodes.RETURN;
 
public class AddVisitorTransformer1 {
    public static void main(String[] args) throws IOException {
        String className = "com.org.xcyz.asm.Student";
        String classJvmName = className.replace('.', '/');
        ClassReader cr = new ClassReader(className);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
        ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9,cw) {
            boolean hasFiled = false;
            @Override
            public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
                if("age".equals(name) && "I".equals(descriptor))hasFiled=true;
                return super.visitField(access, name, descriptor, signature, value);
            }
 
            @Override
            public void visitEnd() {
                if (!hasFiled) {
                    FieldVisitor fieldVisitor = cv.visitField(ACC_PRIVATE, "age", "I", null, null);
                    if (fieldVisitor != null) {
                        fieldVisitor.visitEnd();
                    }
                    MethodVisitor setAge = cv.visitMethod(ACC_PUBLIC, "setAge", "(I)V", null, null);
                    if (setAge != null) {
                        setAge.visitCode();
                        setAge.visitVarInsn(ALOAD,0);
                        setAge.visitVarInsn(ILOAD,1);
                        setAge.visitFieldInsn(PUTFIELD,classJvmName,"age","I");
                        setAge.visitInsn(RETURN);
                        setAge.visitMaxs(0,0);
                        setAge.visitEnd();
                    }
                    MethodVisitor getAge = cv.visitMethod(ACC_PUBLIC, "getAge", "()I", null, null);
                    if (getAge != null) {
                        getAge.visitCode();
                        getAge.visitVarInsn(ALOAD,0);
                        getAge.visitFieldInsn(GETFIELD,classJvmName,"age","I");
                        getAge.visitInsn(IRETURN);
                        getAge.visitMaxs(0,0);
                        getAge.visitEnd();
                    }
                }
                super.visitEnd();
            }
        };
        cr.accept(classVisitor,ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
        byte[] bytes = cw.toByteArray();
        Files.write(Paths.get("target/classes/"+classJvmName+".class"),bytes,TRUNCATE_EXISTING,CREATE);
    }
}

3. 动态添加字段的时机

3.1 visit方法中

java 复制代码
override fun visit(
        version: Int,
        access: Int,
        name: String?,
        signature: String?,
        superName: String?,
        interfaces: Array<out String>?
    ) {
        println("visit access $access className $className name $name  signature $signature")

        val numFiledVisitor = cv.visitField(Opcodes.ACC_PUBLIC, "age", "I", null, 25)
        numFiledVisitor.visitEnd()
        val strFiledVisitor = cv.visitField(Opcodes.ACC_PUBLIC, "str", "Ljava/lang/String;", null, "Hello World")
        strFiledVisitor.visitEnd()

        super.visit(version, access, name, signature, superName, interfaces)
    }

3.2 visitEnd方法中

java 复制代码
override fun visitEnd() {

        val nameFiledVisitor = cv.visitField(Opcodes.ACC_PUBLIC, "name", "Ljava/lang/String;", null, "jdsjlzx")
        nameFiledVisitor.visitEnd()

        super.visitEnd()
    }

4. 动态添加字段的初始化

通过ASM添加的字段,需要在ini方法中进行赋值

java 复制代码
 override fun onMethodEnter() {
        println("onMethodEnter access $access className $className name $name  descriptor $descriptor")


        //动态生成的变量要在init方法中赋值(static变量除外)
        if (name.equals("<init>") && descriptor.equals("()V")) {
            //每个classFile都有一个<init>的初始化方法(固定写法)
            mv.visitVarInsn(Opcodes.ALOAD, 0)
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)
            mv.visitVarInsn(Opcodes.ALOAD, 0)


            //初始化定义的变量的值
            mv.visitIntInsn(BIPUSH, 100)
            mv.visitFieldInsn(PUTFIELD, "com/baidu/main/MainActivity", "age", "I" )

            //String的初始化会在构造方法中
            mv.visitVarInsn(Opcodes.ALOAD, 0)
            mv.visitLdcInsn("Hello World")
            mv.visitFieldInsn(PUTFIELD, "com/baidu/main/MainActivity", "str", "Ljava/lang/String" )

            mv.visitVarInsn(Opcodes.ALOAD, 0)
            mv.visitLdcInsn("jdsjlzx")
            mv.visitFieldInsn(PUTFIELD, "com/baidu/main/MainActivity", "name", "Ljava/lang/String" )

            mv.visitInsn(RETURN)
        }

     
        super.onMethodEnter()
    }

插桩后的class文件如下:

源码:jdsjlzx/AsmPluginProject

完整代码如下:

java 复制代码
package com.baidu.plugin.print

import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.Attribute
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.FieldVisitor
import org.objectweb.asm.Label
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.commons.AdviceAdapter


class PrintClassVisitor(nextVisitor: ClassVisitor, private val className: String): ClassVisitor(ASM9, nextVisitor),
    Opcodes {

    override fun visit(
        version: Int,
        access: Int,
        name: String?,
        signature: String?,
        superName: String?,
        interfaces: Array<out String>?
    ) {
        println("visit access $access className $className name $name  signature $signature")

        val numFiledVisitor = cv.visitField(Opcodes.ACC_PUBLIC, "age", "I", null, 25)
        numFiledVisitor.visitEnd()
        val strFiledVisitor = cv.visitField(Opcodes.ACC_PUBLIC, "str", "Ljava/lang/String;", null, "Hello World")
        strFiledVisitor.visitEnd()

        super.visit(version, access, name, signature, superName, interfaces)
    }


    override fun visitEnd() {

        val nameFiledVisitor = cv.visitField(Opcodes.ACC_PUBLIC, "name", "Ljava/lang/String;", null, "jdsjlzx")
        nameFiledVisitor.visitEnd()

        //准备添加字段age的setAge以及getAge方法(添加之前确保age字段存在)
        val hasAgeField = true;
        if (hasAgeField) {

            var methodVisitor = cv.visitMethod(ACC_PUBLIC, "getAge", "()I", null, null);
            methodVisitor.visitCode();
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitFieldInsn(GETFIELD, "com/baidu/main/MainActivity", "age", "I");
            methodVisitor.visitInsn(IRETURN);
            methodVisitor.visitMaxs(1, 1);
            methodVisitor.visitEnd();


            methodVisitor = cv.visitMethod(ACC_PUBLIC, "setAge", "(I)V", null, null);
            methodVisitor.visitCode();
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitVarInsn(ILOAD, 1);
            methodVisitor.visitFieldInsn(PUTFIELD, "com/baidu/main/MainActivity", "age", "I");
            methodVisitor.visitInsn(RETURN);
            methodVisitor.visitMaxs(2, 2);
            methodVisitor.visitEnd();


        }




        super.visitEnd()
    }


    override fun visitField(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        value: Any?
    ): FieldVisitor {
        //动态生成的file不会触发visitField方法,除非类中有声明的field
        println("visitField access $access className $className name $name  descriptor $descriptor")





        val filedVisitor = super.visitField(access, name, descriptor, signature, value)

        return MyPrintFieldVisitor(
            Opcodes.ASM5,
            filedVisitor
        )
    }

    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions)

        return MyPrintMethodVisitor(
            Opcodes.ASM5,
            methodVisitor,
            access,
            className,
            name,
            descriptor
        )
    }

}

class MyPrintFieldVisitor(api: Int, filedVisitor: FieldVisitor) : FieldVisitor(api, filedVisitor) {

    override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
        println("visitAnnotation descriptor $descriptor visible $visible")


        return super.visitAnnotation(descriptor, visible)
    }


}

class MyPrintMethodVisitor(
    api: Int,
    methodVisitor: MethodVisitor?,
    private val access: Int,
    private val className: String,
    private val name: String?,
    private val descriptor: String?
) : AdviceAdapter(api, methodVisitor, access, name, descriptor) {


    override fun onMethodEnter() {
        println("onMethodEnter access $access className $className name $name  descriptor $descriptor")


        //动态生成的变量要在init方法中赋值(static变量除外)
        if (name.equals("<init>") && descriptor.equals("()V")) {
            //每个classFile都有一个<init>的初始化方法(固定写法)
            mv.visitVarInsn(Opcodes.ALOAD, 0)
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)
            mv.visitVarInsn(Opcodes.ALOAD, 0)


            //初始化定义的变量的值
            mv.visitIntInsn(BIPUSH, 100)
            mv.visitFieldInsn(PUTFIELD, "com/baidu/main/MainActivity", "age", "I" )

            //String的初始化会在构造方法中
            mv.visitVarInsn(Opcodes.ALOAD, 0)
            mv.visitLdcInsn("Hello World")
            mv.visitFieldInsn(PUTFIELD, "com/baidu/main/MainActivity", "str", "Ljava/lang/String" )

            mv.visitVarInsn(Opcodes.ALOAD, 0)
            mv.visitLdcInsn("jdsjlzx")
            mv.visitFieldInsn(PUTFIELD, "com/baidu/main/MainActivity", "name", "Ljava/lang/String" )

            mv.visitInsn(RETURN)
        }

        if (name.equals("sum999")) {
            //System.out.println("999999");
            mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("999999-begin")
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }




        super.onMethodEnter()
    }

    override fun onMethodExit(opcode: Int) {
        println("onMethodExit access $access className $className name $name  descriptor $descriptor")
        if (name.equals("sum999")) {
            //System.out.println("999999");
            mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("999999-end")
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }
        super.onMethodExit(opcode)
    }

    override fun visitFieldInsn(opcode: Int, owner: String?, name: String?, descriptor: String?) {
        println("visitFieldInsn opcode $opcode owner $owner name $name  descriptor $descriptor")
        super.visitFieldInsn(opcode, owner, name, descriptor)
    }

    override fun visitLocalVariable(
        name: String?,
        descriptor: String?,
        signature: String?,
        start: Label?,
        end: Label?,
        index: Int
    ) {
        println("visitLocalVariable name $name  descriptor $descriptor start $start end $end index $index")
        super.visitLocalVariable(name, descriptor, signature, start, end, index)
    }

    override fun visitAttribute(attribute: Attribute?) {
        println("visitAttribute attribute $attribute")
        super.visitAttribute(attribute)
    }

    override fun visitLineNumber(line: Int, start: Label?) {
        println("visitLineNumber line $line start $start")
        super.visitLineNumber(line, start)
    }

    override fun visitFrame(
        type: Int,
        numLocal: Int,
        local: Array<out Any>?,
        numStack: Int,
        stack: Array<out Any>?
    ) {
        println("visitFrame type $type  numLocal $numLocal local $local numStack $numStack stack $stack")
        super.visitFrame(type, numLocal, local, numStack, stack)
    }

    override fun visitLabel(label: Label?) {
        println("visitLabel label $label")
        super.visitLabel(label)
    }


    override fun visitCode() {
        println("visitCode")

        super.visitCode()
    }

    override fun visitMaxs(maxStack: Int, maxLocals: Int) {
        println("visitMaxs maxStack $maxStack maxLocals $maxLocals")
        super.visitMaxs(maxStack, maxLocals)
    }

    override fun visitEnd() {
        println("visitEnd")
        super.visitEnd()
    }
}
相关推荐
无为之士1 分钟前
Linux自动备份Mysql数据库
linux·数据库·mysql
重生之绝世牛码2 分钟前
Java设计模式 —— 【结构型模式】外观模式详解
java·大数据·开发语言·设计模式·设计原则·外观模式
小蜗牛慢慢爬行8 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试
小汤猿人类15 分钟前
open Feign 连接池(性能提升)
数据库
新手小袁_J32 分钟前
JDK11下载安装和配置超详细过程
java·spring cloud·jdk·maven·mybatis·jdk11
呆呆小雅33 分钟前
C#关键字volatile
java·redis·c#
Monly2134 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
阳冬园36 分钟前
mysql数据库 主从同步
数据库·主从同步
Ttang2336 分钟前
Tomcat原理(6)——tomcat完整实现
java·tomcat
钱多多_qdd1 小时前
spring cache源码解析(四)——从@EnableCaching开始来阅读源码
java·spring boot·spring