JDK动态代理的实现原理浅析

本文介绍了JDK动态代理的概念,通过实例展示了如何使用动态代理,包括目标接口、目标对象、InvocationHandler的定义及Proxy.newProxyInstance()的使用。详细解析了Proxy.newProxyInstance()的实现原理,探讨了代理类的生成过程和字节码反编译后的特性,揭示了代理类如何调用InvocationHandler的invoke()方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JDK动态代理简介

什么是JDK动态代理?
先看看代理的概念——代理:为其他对象提供一种代理以控制对这个对象的访问

代理在生活中很常见,比如买火车票可以不去火车站,而是通过代售点;想要逃课了,就让同学代签到等等。
说白了,就是可以通过代理来完成目标事件。(自我理解)

不同于静态代理直接采用编码的方式实现,JDK动态代理是利用反射机制在运行时创建代理类,进而调用相应的方法。

JDK动态代理的使用

通过一个小例子来看看如何使用JDK动态代理。

目标接口
public interface GameService {

    String beginGame(String s);

    void playGame(String s);
}

目标接口中定义了两个目标方法。

目标对象
public class GamePlay implements GameService {

    public String beginGame(String name) {
        System.out.println("玩家" + name + "进入召唤师峡谷,游戏开始!");
        return "玩家名字是:" + name;
    }

    public void playGame(String s) {
        System.out.println("玩家说: " +  s);
    }
}

目标对象是目标接口的实现,也是要被代理的对象。

调用处理器——InvocationHandler
public class GameInvocationHandler implements InvocationHandler {

    private Object target;

    //构造方法(可传入目标对象)
    public GameInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //业务逻辑
        System.out.println("before-----欢迎来到——动态召唤师峡谷");

        //方法的返回值(可以为空)
        Object object = method.invoke(target, args);

        //业务逻辑
        System.out.println("after------欢迎离开——动态召唤师峡谷");
        return object;
    }
}

InvocationHandler是调用处理器接口,它只定义了唯一一个 invoke() 方法,通过重写该方法可自定义代理的业务逻辑,该方法三个参数:

  • proxy:代理类对象($Proxy0),在该方法中一般不会用到
  • method:被代理类调用的方法对象
  • args:方法对象中的参数
创建代理类对象——Proxy.newProxyInstance()
public class GamePlayDynamicProxy {


    public static void main(String[] args) {

        //目标类(实现类)
        GameService gameService = new GamePlay();

        //调用处理器
        InvocationHandler invocationHandler = new GameInvocationHandler(gameService);

        //生成代理对象
        GameService GameServiceproxy = (GameService) Proxy.newProxyInstance(gameService.getClass().getClassLoader(),
                gameService.getClass().getInterfaces(), invocationHandler);

        //调用方法,实际上运行的是真实类的方法(打断点可知)
        GameServiceproxy.playGame("我要超神啦!");

        //beginGame()可获取返回值
        //String resultFromInvoke = GameServiceproxy.beginGame("LBJ");
        //System.out.println("InvocationHandler中的返回值:" + resultFromInvoke);
    }
}

通过Proxy类的静态方法 newProxyInstance() 来创建代理类对象实例,关于其中具体的细节在下面的实现原理中将会提及。

输出:
before-----欢迎来到——动态召唤师峡谷
玩家说: 我要超神啦!
after------欢迎离开——动态召唤师峡谷

可见,通过使用动态代理,可以在目标方法中增加所需要的业务逻辑(如日志处理、事务管理等)。

JDK动态代理使用步骤

1. 定义目标方法的接口(必须)

2. 实现接口目标方法的实现类(不是必须,如RPC中客户端代理没有实现类/Mybatis中的mapper接口也没有实现类,代理过程中实现 )

3. 定义InvocationHandler,在invoke()方法中实现代理的业务逻辑,构造InvocationHandler时可传入必要的参数,以便在invoke()中使用。例如,可传入实现类作为构造参数。

4. 调用Proxy.newProxyInstance()生成代理类对象。该方法传入三个参数

  • ClassLoader:代理对象的类加载器;
  • Class[] interfaces:代理对象需要实现的接口;
  • InvocationHandler:自定义实现的InvocationHandler

5.通过代理类对象运行目标方法。

实现原理

那么 Java 的动态代理是如何实现的?
首先开门见山直接进入生成代理类对象的Proxy.newProxyInstance()方法。

Proxy.newProxyInstance()方法
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
        //克隆传入的接口
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        //通过类加载器和指定接口,生成代理类字节码文件,并获取代理类Class对象
        Class<?> cl = getProxyClass0(loader, intfs);

        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            //获取指定构造函数对象(也就是 $Proxy0(InvocationHandler h))
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //通过构造函数对象生成代理类对象
            return cons.newInstance(new Object[]{h});
        } // catch...省略 
    }

在这个方法中主要做了以下工作(如代码中的注释部分):

  1. 通过类加载器和指定接口,生成代理类字节码文件,并获取代理类Class对象cl;
  2. 通过Class对象cl获取指定的构造函数对象cons;
  3. 构造函数对象cons,通过方法中传入的InvocationHandler,生成一个实例,这个实例就是代理类对象。

可见反射在上述代码中起了重要作用。

大致了解了代理类对象是如何生成的,那么被代理的方法是如何调用的呢? InvocationHandler中重写的invoke()方法又是如何调用的呢?接下来我们就来看看代理类究竟长什么模样。

反编译

参考其他文章可知最终生成代理类的字节码是这样一行代码:

//生成字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

调用了ProxyGenerator.generateProxyClass()方法, 因此我们可以通过该方法测试生成的代理类对象Class文件,并对其进行反编译,查看生成的具体代理类究竟是什么模样。

先获取代理类Class文件:

public static void main(String[] args) {

        byte[] proxyBytes = ProxyGenerator.generateProxyClass("ProxyObject", new Class[]{GameService.class});
        File file = new File("E:\\ProxyObject.class");
        try {
            FileOutputStream out = new FileOutputStream(file);
            out.write(proxyBytes);
            out.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

将获取到的Class文件存入E盘,即ProxyObject.class文件,然后再通过一个在线反编译的网站进行反编译,获取代理类的源码:

import com.proxy.GameService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class ProxyObject
  extends Proxy
  implements GameService
{
  private static Method m1;
  private static Method m2;
  private static Method m4;
  private static Method m0;
  private static Method m3;

  public ProxyObject(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
  {
    try
    {
      return ((Boolean)h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
  {
    try
    {
      return (String)h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final void playGame(String paramString)
  {
    try
    {
      h.invoke(this, m4, new Object[] { paramString });
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode()
  {
    try
    {
      return ((Integer)h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String beginGame(String paramString)
  {
    try
    {
      return (String)h.invoke(this, m3, new Object[] { paramString });
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m4 = Class.forName("com.proxy.GameService").getMethod("playGame", new Class[] { Class.forName("java.lang.String") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m3 = Class.forName("com.proxy.GameService").getMethod("beginGame", new Class[] { Class.forName("java.lang.String") });
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

观察上述代理类源码可以发现,包含了GameService接口中定义的两个方法playGame()和beginGame()。并且,
当代理类对象调用目标方法时,实际上调用的就是InvocationHandler中的invoke()方法,因此也就解释代理类是如何与invoke()方法相关联的。

以playGame方法为例:
public final void playGame(String paramString) 即调用了
 h.invoke(this, m4, new Object[] { paramString });
 h:构建代理类传入的InvocationHandler
 m4: static 代码块中定义的method实例
 paramString:参数
代理类特性

观察上述代理类源码,发现动态生成的代理类有以下特点:

1. 继承了Proxy类,实现了所代理的接口(不能再继承其他类);

2. 除了包含所代理的接口方法,还有Object类的equals()、hashCode()、toString()三个方法,目的应该是防止重写保证和目标类一致;

3. 每一个方法都会执行调用处理器的 invoke() 方法(在其中可实现自定义的逻辑);

4. 提供了一个使用InvocationHandler作为参数的构造方法,通过前面源码分析也能知道;

5. 每个代理类实例都会关联一个调用处理器对象。传入不同的调用处理器对象可生成不同的代理类对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值