返回

基于ASM实现Java动态代理的原理与实战

后端

前言

Java 动态代理是一种常用的技术,用于在运行时为某个类或接口创建动态代理对象。动态代理对象可以拦截对目标对象的调用,并在调用前后执行一些额外的操作。这在许多场景下非常有用,例如日志记录、性能监控、安全检查等。

Java 中有两种常见的动态代理实现方式:一是基于接口的动态代理,二是基于子类的动态代理。基于接口的动态代理使用 java.lang.reflect.Proxy 类来创建动态代理对象,而基于子类的动态代理使用 java.lang.instrument.Instrumentation 类来创建动态代理对象。

基于ASM实现Java动态代理

ASM是开源的Java字节码修改框架,可以理解成Java的字节码处理器。
ASM的使用比较复杂,它有多个模块组成,我们要完成Java动态代理主要使用的模块是ASM的核心ASM,ASM用来解析和操作JVM的字节码,ASM用来修改ClassFile(Class文件)的结构。
基于ASM实现Java动态代理的原理如下:

  1. 创建一个新的类,这个类继承自目标类
  2. 在新类中,重写目标类中的方法,在方法中加入我们想要执行的额外操作
  3. 使用ASM将新类字节码修改成我们想要的样子
  4. 加载新类到JVM中,并实例化新类

ASM实现Java动态代理的步骤

  1. 定义接口
  2. 实现目标类
  3. 创建代理类
  4. 修改字节码
  5. 加载修改后的字节码
  6. 使用代理类

ASM实现Java动态代理的优点

  • 性能优异
  • 灵活度高
  • 可扩展性强

ASM实现Java动态代理的缺点

  • 实现复杂
  • 难以调试

ASM实现Java动态代理的应用场景

  • 日志记录
  • 性能监控
  • 安全检查
  • AOP框架

ASM实现Java动态代理的代码示例

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxyExample {

    public static void main(String[] args) {
        // 目标对象
        Target target = new Target();

        // 创建代理类
        ProxyClassGenerator generator = new ProxyClassGenerator(target.getClass());
        Class<?> proxyClass = generator.generateProxyClass();

        // 创建代理对象
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 在方法调用前后执行额外的操作
                System.out.println("Method " + method.getName() + " called with args " + Arrays.toString(args));
                Object result = method.invoke(target, args);
                System.out.println("Method " + method.getName() + " returned " + result);
                return result;
            }
        };
        Object proxy = Proxy.newProxyInstance(proxyClass.getClassLoader(), new Class<?>[]{target.getClass()}, handler);

        // 使用代理对象
        proxy.method1();
        proxy.method2(1, 2, 3);
    }

    private static class ProxyClassGenerator {

        private Class<?> targetClass;

        public ProxyClassGenerator(Class<?> targetClass) {
            this.targetClass = targetClass;
        }

        public Class<?> generateProxyClass() {
            // 创建新的类
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
            String className = targetClass.getName() + "$Proxy";
            cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", new String[]{targetClass.getName()});

            // 创建构造函数
            MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(L" + targetClass.getName() + ";)V", null, null);
            mv.visitCode();
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, targetClass.getName(), "<init>", "()V", false);
            mv.visitInsn(Opcodes.RETURN);
            mv.visitMaxs(2, 2);
            mv.visitEnd();

            // 创建方法
            for (Method method : targetClass.getMethods()) {
                mv = cw.visitMethod(Opcodes.ACC_PUBLIC, method.getName(), method.getDescriptor(), null, null);
                mv.visitCode();
                // 在方法调用前后执行额外的操作
                mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv.visitLdcInsn("Method " + method.getName() + " called with args ");
                mv.visitVarInsn(Opcodes.ALOAD, 0);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, targetClass.getName(), "toString", "()Ljava/lang/String;", false);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                mv.visitVarInsn(Opcodes.ALOAD, 0);
                for (int i = 0; i < method.getParameterCount(); i++) {
                    mv.visitVarInsn(Opcodes.ALOAD, i + 1);
                }
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, targetClass.getName(), method.getName(), method.getDescriptor(), false);
                mv.visitVarInsn(Opcodes.ASTORE, method.getParameterCount() + 1);
                mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv.visitLdcInsn("Method " + method.getName() + " returned ");
                mv.visitVarInsn(Opcodes.ALOAD, method.getParameterCount() + 1);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                mv.visitVarInsn(Opcodes.ALOAD, method.getParameterCount() + 1);
                mv.visitInsn(Opcodes.ARETURN);
                mv.visitMaxs(method.getParameterCount() + 2, method.getParameterCount() + 3);
                mv.visitEnd();
            }

            cw.visitEnd();

            // 加载修改后的字节码
            byte[] bytes = cw.toByteArray();
            return new ClassLoader() {
                @Override
                public Class<?> findClass(String name) {
                    return defineClass(name, bytes, 0, bytes.length);
                }
            }.findClass(className);
        }
    }

    private static class Target {

        public void method1() {
            System.out.println("method1 called");
        }

        public int method2(int a, int b, int c) {
            System.out.println("method2 called with args " + a + ", " + b + ", " + c);
            return a + b + c;
        }
    }
}

总结

ASM实现Java动态代理是一种非常灵活的实现方式,可以满足各种不同的需求。但是,ASM的使用比较复杂,需要对Java字节码有深入的了解。