前言

JDK动态代理是java自带的一种运行时生成字节码对目标对象进行代理的机制。
本篇主要介绍下JDK动态代理的实现原理。


示例

先来看一个jdk动态代理的示例(ChatGPT生成):

// 创建一个接口
public interface Calculator {
    int add(int a, int b);
    int subtract(int a, int b);
}


// 该接口的实现
public class NumCalculator implements Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    public int subtract(int a, int b) {
        return a - b;
    }
}

// 定义一个 JDK 动态代理处理类,实现 InvocationHandler 接口
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * 代理类,JDK动态代理,代理接口 Calculator,其实不一定代理Calculator接口,bind方法返回的是Object
 * 代理类测试见
 */
public class CalculatorProxy implements InvocationHandler {
	/**
     * 存储代理接口
     */
	private Object target;

    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                                      target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理方法执行前的操作");
        Object result = method.invoke(target, args);
        System.out.println("代理方法执行后的操作");
        return result;
    }
}

最后,我们可以在测试代码中使用这个代理对象:

@Test
    public void testProxy () {
        CalculatorProxy proxy = new CalculatorProxy();
        Calculator calculator = (Calculator) proxy.bind(new NumCalculator());
        // buildProxy(); // 后面会说此函数的用处
        int result = calculator.add(1, 2);
        System.out.println("Result: " + result);
    }

执行结果如下:

代理方法执行前的操作
代理方法执行后的操作
Result: 3


原理

关键类/接口

1、java.lang.reflect.Proxy
实现动态代理的核心类,提供了创建代理对象的静态方法。

2、java.lang.reflect.InvocationHandler
该接口负责实际处理动态代理对象的方法调用,它是一个接口,用户需要自定义实现类实现该接口来提供代理对象的行为(定义代理行为)。

根据示例看一下职责:

  • Proxy:创建代理对象,提供对应静态方法
  • InvocationHandler:接口的实现类定义代理对象的行为
  • Calculator calculator = (Calculator) proxy.bind(new NumCalculator()); calculator为代理对象引用
  • Calculator$proxy:代理类(下文展开)

调用代理类的原理

为什么调用代理对象的方法时,会调用到InvocationHandler实现类的方法,进而调用目标函数呢?

示例中调用如下代码,会执行CalculatorProxy类的invoke方法,再调用到NumCalculator的add方法:

int result = calculator.add(1, 2);

主要看Proxy.newProxyInstance()创建的到底什么:

debug:
image-1676874829326
可以看到,Proxy.newProxyInstance返回的是一个代理类,另外有一个
h字段,类型为CalculatorProxy。那这个代理代理类具体长什么样子呢?

插播下:我们都知道java的多态的机制:一个变量可以指向该变量对应类的子类实例

可以通过示例中的测试代码中的 buildProxy(); 进行生成对应的class文件,具体方法实现如下:

public void buildProxy() {
        byte[] bytes = ProxyGenerator.generateProxyClass("Calculator$proxy", new Class[]{Calculator.class});
        String fileName = System.getProperty("user.dir")+"/target/Calculator$proxy.class";
        try {
            File file = new File(fileName);
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write(bytes);
            fileOutputStream.flush();
            fileOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

执行后,结果生成 Calculator$proxy.class:
image-1676875430526

idea反编译查看:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import com.mainto.service.proxy.Calculator;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class Calculator$proxy extends Proxy implements Calculator {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    public Calculator$proxy(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int add(int var1, int var2) throws  {
        try {
            return (Integer)super.h.invoke(this, m4, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int subtract(int var1, int var2) throws  {
        try {
            return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.mainto.service.proxy.Calculator").getMethod("add", Integer.TYPE, Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.mainto.service.proxy.Calculator").getMethod("subtract", Integer.TYPE, Integer.TYPE);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

Calculator$proxy代理类,继承于Proxy,实现了代理的接口。
里面的静态代码块,通过反射生成代理接口中的2个方法引用(另外3个是Object的方法);同时代理类也实现了代理接口中的方法。

当调用代理对象的方法时,根据多态的机制,会调用真实的实现类的方法,即代理类中的方法;方法的实现,则是调用InvocationHandler实现类实现了代理行为的方法,最后调用到目标方法,且完成了代理行为。

字节码生成

动态代理类是如何生成的呢,上面也提到了,使用了Proxy.newProxyInstance静态方法完成。

debug:
最终会调用sun.misc.ProxyGenerator#generateClassFile,通过字节码拼接完成代理类的动态生成。
image-1676876219993


扩展

JDK动态代理和我们常说的设计模式中的代理模式有什么区别

相同点:
都是代理模式,当客户端访问代理对象时,代理对象会将请求转发给目标对象,同时代理对象可以在请求前后进行一些操作,如校验、日志记录等

不同点:
1、JDK动态代理,属于动态代理,不用编写源代码;代理模式是静态代理
2、JDK动态代理通过反射动态生成的;静态代理需要编码
3、JDK动态代理统一代理行为;静态代理可以随意每个方法的代理行为

为什么JDK代理的对象必须实现接口

通过查看代理类的字节码可以看到代理类:
public final class Calculator$proxy extends Proxy implements Calculator {}

它通过实现Proxy类,传入InvocationHandler的实现,完成代理行为的调用。java是单继承的,所以这里需要通过实现接口的方式完成;另一个点我想可能是能保证实现接口中所有方法,来达成所有方法的代理,也是一种约定,如果是类的话就可实现可不实现了。

代理接口没有实现类的写法

还是上面的例子,可能框架设计时用泛型完成目标功能的实现等,参考:

// 创建一个接口
public interface Calculator {
    int add(int a, int b);
    int subtract(int a, int b);
}

// 定义一个 JDK 动态代理处理类,实现 InvocationHandler 接口
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * 代理类,JDK动态代理,代理接口 Calculator,其实不一定代理Calculator接口,bind方法返回的是Object
 * 代理类测试见
 */
public class CalculatorProxy implements InvocationHandler {
	/**
     * 存储代理接口
     */
	private Class target;

    public Object bind(Class target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClassLoader(),
                new Class[]{target}, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理方法执行前的操作");
        // Object result = method.invoke(target, args);
        System.out.println("代理方法执行后的操作");
        return result;
    }
}

// 使用时,直接用接口绑定
Calculator calculator = (Calculator) proxy.bind(Calculator.class);