Java的字节码操作框架ASM详解

2023-12-20 15:57:41

一、ASM介绍

1. 简介

ASM是一个用于在Java字节码级别进行操作的框架,它运行你在不破坏原有逻辑的情况下修改、生成和转换字节码。ASM提供类一种灵活高效的方式,用于在编译器运行时动态生成字节码,这对于许多Java框架和工具来说是非常有用的。

2. 基本概念

字节码:字节码是Java源代码编译后的中间表达形式,它由字节码指令组成,这些指令被Java虚拟机执行。

访问者模式:ASM使用访问者模式,它通过访问者来访问和操作字节码。在ASM中,你可以实现自定义的访问者来处理不同类型的字节码元素。

类适配器和方法适配器:ASM提供了类适配器和方法适配器,用于修改类和方法的字节码。类适配器可以用于全局类操作,而方法适配器则可以用于单个方法的操作。

3. Java ASM的应用场景

  1. 代码分析和优化:通过ASM,我们可以对字节码进行深入分析,以找出潜在的性能瓶颈或者执行特定的优化
  2. 动态代理和AOP(面向切面编程):ASM可以用于创建动态代理或实现AOP框架,从而实现运行时的行为修改
  3. 自定义类加载器:通过ASM,我们可以实现自定义类加载器,以支持独特的类加载策略和实现热加载等功能
  4. 安全审计:ASM可以用于分析潜在的安全风险,例如检测恶意代码或验证第三方库的安全性

二、ASM基本使用

1. 基本使用

添加ASM依赖

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>9.2</version> <!-- 版本号可能有更新 -->
</dependency>

生成一个类

public class HelloWorldGenerator {
    public static byte[] generateHelloWorldClass() {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        // Define the class
        cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "HelloWorld", null, "java/lang/Object", null);
        // Define the constructor
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        mv.visitInsn(Opcodes.RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();

        // Define the main method
        mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
        mv.visitCode();
        mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        mv.visitLdcInsn("Hello, ASM!");
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        mv.visitInsn(Opcodes.RETURN);
        mv.visitMaxs(2, 2);
        mv.visitEnd();
        cw.visitEnd();
        return cw.toByteArray();
    }
}

使用生成的类:


public class HelloWorldRunner {
    public static void main(String[] args) {
        byte[] classData = HelloWorldGenerator.generateHelloWorldClass();
        CustomClassLoader loader = new CustomClassLoader();
        Class<?> helloWorldClass = loader.defineClass("HelloWorld", classData);

        try {
            // Invoke the main method
            helloWorldClass.getMethod("main", String[].class).invoke(null, (Object) args);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Custom class loader to define a class from byte array
    static class CustomClassLoader extends ClassLoader {
        public Class<?> defineClass(String name, byte[] b) {
            return defineClass(name, b, 0, b.length);
        }
    }
}

在这里插入图片描述

2. Java ASM主要组件

JavaASM提供了的主要组件包括ClassReaderClassWriterClassVisitorMethodVIsitor

(1)ClassReader(类读取器)

ClassReader用于读取已经存在的类文件的字节码,并以一种结构化的方式提供对类结构的访问。它能够解析类的结构,包括类的字段、方法和注解信息。

accept(ClassVisitor classVisitor,int flags)
//将类的结构传递给给定的ClassVisitor,flags参数用于指定读取选项

(2)ClassWriter(类写入器)
ClassWriter用于生产新的类文件的字节码。你可以通过添加新的字段、方法等,最终生成一个新的类字节码

visit(int version, int access, String name, String signature, String superName, String[] interfaces)
//开始访问一个类,指定类的版本、访问标志、类名、泛型签名、父类和实现的接口。
visitMethod(int access, String name, String descriptor, String signature, String[] exceptions)
//开始访问一个方法,指定方法的访问标志、方法名、描述符、泛型签名和可能抛出的异常。

(3)ClassVisitor(类访问器):
ClassVisitor是一个接口,用于遍历和修改类的结构。你可以自定义一个ClassVisitor,重写其方法,在访问类、方法、字段等时执行你的逻辑。

visit(int version, int access, String name, String signature, String superName, String[] interfaces) 
//开始访问一个类。
visitMethod(int access, String name, String descriptor, String signature, String[] exceptions)
//开始访问一个方法。
visitField(int access, String name, String descriptor, String signature, Object value)
//访问类的字段。

(4)MethodVisitor(方法访问器)
MethodVisitor 类似于 ClassVisitor,但专注于方法级别的访问和修改。你可以在访问方法、局部变量、指令等时插入你的操作。

visitCode()
//访问方法的代码开始部分。
visitInsn(int opcode)
//访问一个无操作指令。
visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)
//访问方法调用指令

这些组件共同构成了ASM的核心,使得开发人员能够以精细的方式操作Java字节码。通过创建自定义的 ClassVisitor 和 MethodVisitor,你可以在字节码层面执行各种操作,包括字节码增强、代码注入、AOP等。

3. Java ASM使用组件准备

创建一个简单的Java类

public class MyClass {
    public void myMethod() {
        System.out.println("Original method");
    }

    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.myMethod();
    }
}

创建ClassReader
要读取字节码,我们首先要创建一个ClassReader实例。ClassReader可以接收一个字节数组,表示已编译的Java类文件。

  //从文件系统中加载字节码
        byte[] bytecode= Files.readAllBytes(Paths.get("/Users/jackchai/Desktop/Self-study-notes/Redis/redis_project/redis_test/target/classes/com/jack/redis/pojo/MyClass.class"));
        //创建ClassReader实例
        ClassReader classReader=new ClassReader(bytecode);

使用ClassVisitor解析字节码

要解析字节码,我们需要创建一个自定义的ClassVisitor实现。以下是一个简单的案例:

public class MyClassVisitor extends ClassVisitor {
    //使用ASM,指定ASM的版本为5
    public MyClassVisitor(){
        //调用父类的构造函数,指定ASM的版本为5
        super(Opcodes.ASM5);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String supername, String[] interfaces) {
        //打印类名
        System.out.println("Class:"+s);
        //调用父类的visit方法,以便继续处理类信息
        super.visit(version, access, name, signature, supername, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        //打印方法名
        System.out.println("Method:"+name);
        return super.visitMethod(access, name, descriptor, signature, exceptions);
    }
}

Visit()方法参数说明:

version:类文件的版本号,表示类文件的JDK版本。例如,JDK1.8的版本是52(0x34),JDK 11 对应的版本为55(0x37)
access:类访问标志,表示类的访问权限和属性
name:类的内部名称,如我们这个类就是com/jack/redis/pojo/MyClass
sinature:类的范型签名,如果没有范型信息,则为null
superName:父类的内部名。这里为java.lang.Object
interfaces:类实现的接口的内部名称数组

visitMethod()方法参数说明:
access:方法访问标志
name:放啊法的名称
descriptor:方法的描述符,表示方法的参数类型和返回值类型
signature:放啊法的范型信息
exceptions:方法抛出的异常的内部名称数组

public class LogInjector {
    public static void main(String[] args) throws IOException {
        //从文件系统中加载字节码
        byte[] bytecode= Files.readAllBytes(Paths.get("/Users/jackchai/Desktop/Self-study-notes/Redis/redis_project/redis_test/target/classes/com/jack/redis/pojo/MyClass.class"));
        //创建ClassReader实例
        ClassReader classReader=new ClassReader(bytecode);
        MyClassVisitor classVisitor=new MyClassVisitor();
        //使用ClassReader的accept方法,将MyClassVisitor传递给ClassReader进行字节码解析
        classReader.accept(classVisitor,0);
    }
}

在这里插入图片描述
上面解析字节码需要的基本组件都已经初始化完毕,下面开始真正解析字节码:

4. 修改字节码

  • 添加、修改和删除字节码

首先修改一下MyClass类


public class MyClass {
    private String name;
    private String type;
    public void myMethod() {
        System.out.println("Original method");
    }

    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.myMethod();
    }
}

要添加、修改或删除字节码,我们需要扩展ClassVisitor类并重写visitField方法。

public class MyFieldClassVisitor extends ClassVisitor {
    public MyFieldClassVisitor(ClassVisitor classVisitor){
        super(Opcodes.ASM5,classVisitor);
    }

    //visitField在访问类的字段时被调用,用于对字段进行修改
    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        //删除MyClass类的name字段
        if("name".equals(s)){
        //返回null表示删除字段
            return null;
        }
        return super.visitField(access, name,descriptor, signature, value);
    }
    //visitEnd方法在访问类结尾时被调用,用于在类结尾添加新的字段
    @Override
    public void visitEnd() {
        //添加名为"newField"的字段
        FieldVisitor fieldVisitor=super.visitField(Opcodes.ACC_PRIVATE,"newField","Ljava/lang/String;",null,null);
        if(fieldVisitor!=null){
            fieldVisitor.visitEnd();
        }
        super.visitEnd();
    }
}

测试代码如下:

public class LogInjector {
    public static void main(String[] args) {
        try {
            // 读取原始类的字节码
            byte[] originalClassBytes = readClassBytes("MyClass.class");
            // 使用 MyFieldClassVisitor 修改类
            byte[] modifiedClassBytes = modifyClass(originalClassBytes);
            // 将修改后的字节码保存到文件
            saveToFile("MyModifiedClass.class", modifiedClassBytes);
            // 运行修改后的类
            runModifiedClass();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static byte[] readClassBytes(String className) throws IOException {
        // 从类文件读取字节码
        // 这里假设 MyClass.class 文件位于当前工程目录下
        // 实际应用中,你可能需要根据你的工程结构调整路径
        return Files.readAllBytes(Paths.get("/Users/jackchai/Desktop/Self-study-notes/Redis/redis_project/redis_test/target/classes/com/jack/redis/pojo/MyClass.class"));
    }
    private static byte[] modifyClass(byte[] originalClassBytes) {
        // 使用 MyFieldClassVisitor 修改类字节码
        ClassReader classReader = new ClassReader(originalClassBytes);
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        MyFieldClassVisitor myFieldClassVisitor = new MyFieldClassVisitor(classWriter);
        classReader.accept(myFieldClassVisitor, ClassReader.SKIP_DEBUG);
        // 返回修改后的字节码
        return classWriter.toByteArray();
    }
    private static void saveToFile(String fileName, byte[] data) throws IOException {
        // 将字节码保存到文件
        try (FileOutputStream fos = new FileOutputStream(fileName)) {
            fos.write(data);
        }
    }
    private static void runModifiedClass() {
        // 运行修改后的类
        MyClass modifiedClass = new MyClass();
        System.out.println(modifiedClass);
    }
}

可以看到修改后的class文件一

  • 添加、修改和删除方法

要添加、修改和删除方法,我们需要扩展ClassVisitor类并重写visitMethod方法。

public class MyMethodClassVisitor extends ClassVisitor {
    public MyMethodClassVisitor(ClassVisitor classVisitor){
        super(Opcodes.ASM5,classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        //删除myMethod方法
        if("myMethod".equals(name)){
            return null;
        }
        return super.visitMethod(access, name, descriptor, signature, exceptions);
    }

    @Override
    public void visitEnd() {
        //添加名为"newMethod"方法
        MethodVisitor methodVisitor=super.visitMethod(Opcodes.ACC_PUBLIC,"newMethod","()V",null,null);
        if(methodVisitor!=null){
            //开始访问方法的字节码
            methodVisitor.visitCode();
            //向方法字节码中添加Returen指令,表示方法返回
            methodVisitor.visitInsn(Opcodes.RETURN);
            //设置方法的最大操作树栈深度和局部变量大小,这里我们是一个空方法
            methodVisitor.visitMaxs(0,0);

            methodVisitor.visitEnd();
        }
        super.visitEnd();
    }
}

下面我们来测试一下生成的字节码是怎么样子的

public class LogInjector {
    public static void main(String[] args) {
        try {
            // 读取原始类的字节码
            byte[] originalClassBytes = readClassBytes("MyClass.class");
            // 使用 MyFieldClassVisitor 修改类
            byte[] modifiedClassBytes = modifyClass(originalClassBytes);
            // 将修改后的字节码保存到文件
            saveToFile("MyModifiedClass.class", modifiedClassBytes);
            // 运行修改后的类
            runModifiedClass();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static byte[] readClassBytes(String className) throws IOException {
        // 从类文件读取字节码
        // 这里假设 MyClass.class 文件位于当前工程目录下
        // 实际应用中,你可能需要根据你的工程结构调整路径
        return Files.readAllBytes(Paths.get("/Users/jackchai/Desktop/Self-study-notes/Redis/redis_project/redis_test/target/classes/com/jack/redis/pojo/MyClass.class"));
    }
    private static byte[] modifyClass(byte[] originalClassBytes) {
        // 使用 MyFieldClassVisitor 修改类字节码
        ClassReader classReader = new ClassReader(originalClassBytes);
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        MyMethodClassVisitor myMethodClassVisitor = new MyMethodClassVisitor(classWriter);
        classReader.accept(myMethodClassVisitor, ClassReader.SKIP_DEBUG);
        // 返回修改后的字节码
        return classWriter.toByteArray();
    }
    private static void saveToFile(String fileName, byte[] data) throws IOException {
        // 将字节码保存到文件
        try (FileOutputStream fos = new FileOutputStream(fileName)) {
            fos.write(data);
        }
    }
    private static void runModifiedClass() {
        // 运行修改后的类
        MyClass modifiedClass = new MyClass();
        System.out.println(modifiedClass);
    }
}

成功删除了原来的myMethod方法,并添加了新的方法
在这里插入图片描述

  • 修改的方法中的指令

要修改方法中的指令,我们需要扩展MethodVisitor类并重写相应的visit方法。下面这个示例表示在每个方法调用前添加一条打印日志的指令:

public class MyMethodAdapter extends MethodVisitor {
    public MyMethodAdapter(MethodVisitor methodVisitor){
        super(Opcodes.ASM5,methodVisitor);
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
        //在方法调用前添加打印日志
        mv.visitFieldInsn(Opcodes.GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream");
        mv.visitLdcInsn("Enterning method:"+name);
        //熟悉字节码结构的都知道,这是在向字节码文件中插入内容
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL,"java/io/PringStream","println","(Ljava/lang/String;)V",false);
        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
    }
}

要应用这个方法适配器,我们需要在自定义的ClassVisitor实现中重写visitMethod方法

public class MyMethodLoggerClassVisitor extends ClassVisitor {
    public MyMethodLoggerClassVisitor(ClassVisitor classVisitor){
        super(Opcodes.ASM5,classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor methodVisitor=super.visitMethod(access,name,descriptor,signature,exceptions);
        return new MyMethodAdapter(methodVisitor);
    }
}

测试方法如下:

public class LogInjector {
    public static void main(String[] args) {
        try {
            // 读取原始类的字节码
            byte[] originalClassBytes = readClassBytes("MyClass.class");
            // 使用 MyFieldClassVisitor 修改类
            byte[] modifiedClassBytes = modifyClass(originalClassBytes);
            // 将修改后的字节码保存到文件
            saveToFile("MyModifiedClass.class", modifiedClassBytes);
            // 运行修改后的类
            runModifiedClass();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static byte[] readClassBytes(String className) throws IOException {
        // 从类文件读取字节码
        // 这里假设 MyClass.class 文件位于当前工程目录下
        // 实际应用中,你可能需要根据你的工程结构调整路径
        return Files.readAllBytes(Paths.get("/Users/jackchai/Desktop/Self-study-notes/Redis/redis_project/redis_test/target/classes/com/jack/redis/pojo/MyClass.class"));
    }
    private static byte[] modifyClass(byte[] originalClassBytes) {
        // 使用 MyFieldClassVisitor 修改类字节码
        ClassReader classReader = new ClassReader(originalClassBytes);
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        MyMethodLoggerClassVisitor myMethodClassVisitor = new MyMethodLoggerClassVisitor(classWriter);
        classReader.accept(myMethodClassVisitor, ClassReader.SKIP_DEBUG);
        // 返回修改后的字节码
        return classWriter.toByteArray();
    }
    private static void saveToFile(String fileName, byte[] data) throws IOException {
        // 将字节码保存到文件
        try (FileOutputStream fos = new FileOutputStream(fileName)) {
            fos.write(data);
        }
    }
    private static void runModifiedClass() {
        // 运行修改后的类
        MyClass modifiedClass = new MyClass();
        System.out.println(modifiedClass);
    }
}

可以看出日志添加成功
在这里插入图片描述

  • 生成新的字节码

在修改字节码后,我们需要使用ClassWriter (它是ClassVisitor的子类)生成新的字节码。上面提供的几个测试代码都用到了ClassWriter

 private static byte[] modifyClass(byte[] originalClassBytes) {
        // 使用 MyFieldClassVisitor 修改类字节码
        ClassReader classReader = new ClassReader(originalClassBytes);
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        MyMethodLoggerClassVisitor myMethodClassVisitor = new MyMethodLoggerClassVisitor(classWriter);
        classReader.accept(myMethodClassVisitor, ClassReader.SKIP_DEBUG);
        // 返回修改后的字节码
        return classWriter.toByteArray();
    }

三、Java ASM高级技巧

1. 自定义类加载器

要实现自定义类加载器,我们需要扩展Java标准裤的ClassLoader,并重写findClass方法。以下展示了如何实现一个简单的自定义类加载器,它使用Java ASM修改类字节码后加载类:

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

public class MyClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 加载原始字节码
            String resourceName = name.replace('.', '/').concat(".class");
            InputStream is = getClass().getClassLoader().getResourceAsStream(resourceName);
            byte[] originalBytecode = is.readAllBytes();

            // 使用 Java ASM 修改字节码
            byte[] modifiedBytecode = modifyBytecode(originalBytecode);

            // 使用修改后的字节码定义类
            ByteBuffer byteBuffer = ByteBuffer.wrap(modifiedBytecode);
            return defineClass(name, byteBuffer, null);
        } catch (IOException e) {
            throw new ClassNotFoundException("Failed to load class " + name, e);
        }
    }

    private byte[] modifyBytecode(byte[] originalBytecode) {
        // 创建 ClassReader 和 ClassWriter
        ClassReader classReader = new ClassReader(originalBytecode);
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);

        // 创建自定义的 ClassVisitor(执行切入动作)
        MyMethodLoggerClassVisitor myMethodLoggerClassVisitor = new MyMethodLoggerClassVisitor(classWriter);

        // 使用 ClassReader 遍历字节码,应用自定义的 ClassVisitor
        classReader.accept(myMethodLoggerClassVisitor, ClassReader.EXPAND_FRAMES);

        // 返回修改后的字节码
        return classWriter.toByteArray();
    }
}

2. 动态代理

动态代理是一种常用的设计模式,可以在运行时动态地为对象生成代理。使用 Java ASM,我们可以生成字节码来实现动态代理。以下是一个简单的动态代理示例,它实现了一个基于接口的代理:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

public class MyDynamicProxy {

    public static <T> T createProxy(Class<T> interfaceClass, InvocationHandler handler) {
        // 创建 ClassWriter
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        // 定义代理类,实现指定的接口
        String proxyClassName = interfaceClass.getName() + "$Proxy";
        String proxyClassInternalName = proxyClassName.replace('.', '/');
        classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, proxyClassInternalName, null, "java/lang/Object", new String[]{Type.getInternalName(interfaceClass)});
        // 实现代理类的构造方法
        MethodVisitor constructorVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        constructorVisitor.visitCode();
            constructorVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            constructorVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            constructorVisitor.visitInsn(Opcodes.RETURN);
            constructorVisitor.visitMaxs(1, 1);
            constructorVisitor.visitEnd();
        // 实现接口的所有方法
        for (Method method : interfaceClass.getDeclaredMethods()) {
            String methodName = method.getName();
            String methodDescriptor = Type.getMethodDescriptor(method);
            MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, methodName, methodDescriptor, null, null);
            // 在代理方法中调用 InvocationHandler 的 invoke 方法
            methodVisitor.visitCode();
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitFieldInsn(Opcodes.GETFIELD, proxyClassInternalName, "handler", "Ljava/lang/reflect/InvocationHandler;");
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitLdcInsn(Type.getType(interfaceClass));
            methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/reflect/Method", "valueOf", "(Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
            methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
            methodVisitor.visitInsn(Opcodes.ARETURN);
            methodVisitor.visitMaxs(4, 2);
            methodVisitor.visitEnd();
        }
        classWriter.visitEnd();
        // 使用自定义类加载器加载代理类
        byte[] proxyClassBytecode = classWriter.toByteArray();
        MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> proxyClass = myClassLoader.defineClass(proxyClassName, ByteBuffer.wrap(proxyClassBytecode), null);
        // 创建代理类实例,并设置 InvocationHandler
        try {
            T proxyInstance = (T) proxyClass.getConstructor().newInstance();
            proxyClass.getField("handler").set(proxyInstance, handler);
            return proxyInstance;
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException("Failed to create proxy instance", e);
        }
        }
}

可以使用MyDynamicProxy为接口创建动态代理

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

public class MyDynamicProxy {

    public static <T> T createProxy(Class<T> interfaceClass, InvocationHandler handler) {
        // 创建 ClassWriter
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);

        // 定义代理类,实现指定的接口
        String proxyClassName = interfaceClass.getName() + "$Proxy";
        String proxyClassInternalName = proxyClassName.replace('.', '/');
        classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, proxyClassInternalName, null, "java/lang/Object", new String[]{Type.getInternalName(interfaceClass)});

        // 实现代理类的构造方法
        MethodVisitor constructorVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        constructorVisitor.visitCode();
            constructorVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            constructorVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            constructorVisitor.visitInsn(Opcodes.RETURN);
            constructorVisitor.visitMaxs(1, 1);
            constructorVisitor.visitEnd();

        // 实现接口的所有方法
        for (Method method : interfaceClass.getDeclaredMethods()) {
            String methodName = method.getName();
            String methodDescriptor = Type.getMethodDescriptor(method);
            MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, methodName, methodDescriptor, null, null);

            // 在代理方法中调用 InvocationHandler 的 invoke 方法
            methodVisitor.visitCode();
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitFieldInsn(Opcodes.GETFIELD, proxyClassInternalName, "handler", "Ljava/lang/reflect/InvocationHandler;");
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitLdcInsn(Type.getType(interfaceClass));
            methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/reflect/Method", "valueOf", "(Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
            methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
            methodVisitor.visitInsn(Opcodes.ARETURN);
            methodVisitor.visitMaxs(4, 2);
            methodVisitor.visitEnd();
        }

        classWriter.visitEnd();

        // 使用自定义类加载器加载代理类
        byte[] proxyClassBytecode = classWriter.toByteArray();
        MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> proxyClass = myClassLoader.defineClass(proxyClassName, ByteBuffer.wrap(proxyClassBytecode), null);

        // 创建代理类实例,并设置 InvocationHandler
        try {
            T proxyInstance = (T) proxyClass.getConstructor().newInstance();
            proxyClass.getField("handler").set(proxyInstance, handler);
            return proxyInstance;
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException("Failed to create proxy instance", e);
        }
        }
}

文章来源:https://blog.csdn.net/qq_43456605/article/details/135100001
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。