java之动态代理

### 目录

  • [@TOC(目录)](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:Class<?>[] interfaces (接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结)
  • [前言](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:Class<?>[] interfaces (接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结)
  • [1. 比喻理解](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:Class<?>[] interfaces (接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结)
  • [2. 代码理解](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:Class<?>[] interfaces (接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结)
  • [1. 签名](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:Class<?>[] interfaces (接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结)
  • [2. 参数1:ClassLoader loader (类加载器)](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:Class<?>[] interfaces (接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结)
  • [3. 参数2:`Class<?>\[\] interfaces` (接口数组)](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:Class<?>[] interfaces (接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结)
  • [4. 参数3:InvocationHandler h (调用处理器)](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:Class<?>[] interfaces (接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结)
  • [3. 测试与运行](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:Class<?>[] interfaces (接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结)
  • [4. 代理字节码原理](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:Class<?>[] interfaces (接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结)
  • [总结](#目录 @TOC 前言 1. 比喻理解 2. 代码理解 1. 签名 2. 参数1:ClassLoader loader (类加载器) 3. 参数2:Class<?>[] interfaces (接口数组) 4. 参数3:InvocationHandler h (调用处理器) 3. 测试与运行 4. 代理字节码原理 总结)

前言

通过一个通俗易懂的比喻,快速理解 Java 动态代理的核心设计思想。

通过手写 JDK 动态代理的完整代码,详细剖析其核心参数与运行机制,并深入 JVM 内存中的代理类字节码,揭示动态代理的底层实现原理。


1. 比喻理解

例子:

  • 大明星(小美):十分擅长唱歌和跳舞。打算举办演唱会,举办演唱会的过程非常繁琐。小美只想把核心精力放在唱歌和跳舞上。
  • 代理(小帅):专门负责处理小美唱歌和跳舞之外的所有繁杂事务。
  • 赞助商 :希望小美来唱歌时,不能直接和小美对话,必须先和经纪人小帅交涉。
  • 调用过程:小帅和赞助商谈妥后(前置准备),把小美带到舞台上,小美只管唱歌(核心业务)。唱完歌后,小帅还要负责结算收尾工作(后置收尾)。

在这个过程中,小帅 就是小美 的代理人,也就是我们代码中常说的代理对象


2. 代码理解

我们首先写一个接口,叫做 Star(明星),声明两个方法:singdance

java 复制代码
public interface Star {
    String sing(String songName);
    void dance();
}

小美是大明星,实现 Star 接口,擅长 singdance

java 复制代码
public class BigStar implements Star {
    @Override
    public String sing(String songName) {
        System.out.println("小美正在唱" + songName);
        return "谢谢大家";
    }
    
    @Override
    public void dance() {
        System.out.println("小美正在跳舞");
    }
}

接下来,我们编写用于创建代理对象的工具类 ProxyUtil

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyUtil {
    
    public static Star createProxy(Star star) {
      
        return (Star) Proxy.newProxyInstance(
            ProxyUtil.class.getClassLoader(),//参数1
            new Class<?>[]{Star.class},//参数2
            new InvocationHandler() {//参数3
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                 
                    if ("sing".equals(method.getName()) || "dance".equals(method.getName())) {
                        System.out.println("找演唱会地点");     
                        Object result = method.invoke(star, args);
                        System.out.println("收尾");
                        return result;
                    }
                    return method.invoke(star, args);
                }
            }
        );
    }
}

为了让代理对象正常工作,Proxy.newProxyInstance 必须明确以下三个参数:

1. 签名
java 复制代码
public static Object newProxyInstance(
    ClassLoader loader,  
    Class<?>[] interfaces, 
    InvocationHandler h
)
2. 参数1:ClassLoader loader (类加载器)

在程序运行过程中,动态代理会在内存中实时生成代理类的字节码。为了让 JVM 识别并加载该字节码,需要提供一个类加载器。通常使用当前工具类的类加载器 ProxyUtil.class.getClassLoader() 或被代理类的类加载器。

3. 参数2:Class<?>[] interfaces (接口数组)

此参数表示代理类要实现哪些接口。

在我们的例子中,代理要代替明星唱歌跳舞,因此需要实现 Star 接口,写为 new Class<?>[]{Star.class}

4. 参数3:InvocationHandler h (调用处理器)

当外部调用代理对象的任何方法时,都会转发到此处理器的 invoke 方法中执行。


3. 测试与运行

我们编写 Main 类进行测试:

java 复制代码
public class Main {  
    public static void main(String[] args) {  
        // 1. 创建真实对象
        BigStar bigStar = new BigStar();  
        // 2. 创建代理对象
        Star proxyStar = ProxyUtil.createProxy(bigStar);  
        // 3. 调用代理方法
        String ret = proxyStar.sing("Lucky");  
        System.out.println(ret);
    }  
}

运行后,控制台输出如下:

text 复制代码
找演唱会地点
小美正在唱Lucky
收尾
谢谢大家

4. 代理字节码原理

看看代理对象的伪代码:

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Star {
    
    private static Method m1; // 代表 Object.equals()
    private static Method m2; // 代表 Object.toString()
    private static Method m3; // 代表 Star.sing()
    private static Method m4; // 代表 Star.dance()

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Object.class);
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("Star").getMethod("sing", String.class);
            m4 = Class.forName("Star").getMethod("dance");
        } catch (Exception e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    public $Proxy0(InvocationHandler h) {
        super(h); // 将处理器传递给父类 java.lang.reflect.Proxy
    }

    @Override
    public final String sing(String songName) {
        try {
            // 所有方法调用都会转发到超类持有的 InvocationHandler 的 invoke 方法中
            return (String) super.h.invoke(this, m3, new Object[]{songName});
        } catch (Error | RuntimeException e) {
            throw e;
        } catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }

    @Override
    public final void dance() {
        try {
            super.h.invoke(this, m4, null);
        } catch (Error | RuntimeException e) {
            throw e;
        } catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
}

动态生成的代理类命名为 $Proxy0,它继承了 java.lang.reflect.Proxy 并且实现了目标接口 Star。Java 不支持多重继承,$Proxy0 已经占用了继承 Proxy 的名额。所以只能代理接口,无法代理实例化的类.

$Proxy0 的构造方法接收一个 InvocationHandler h,并通过 super(h) 传给父类 Proxy 统一保管。

在重写的接口方法(如 singdance)中,没有包含任何业务逻辑,而是直接调用 super.h.invoke(this, method, args).


总结

  1. 动态代理的核心思想是解耦,通过代理对象将繁琐的非核心业务与真实对象的核心业务隔离开来。
  2. JDK 动态代理利用 java.lang.reflect.Proxy 类的 newProxyInstance 方法,在运行期动态地在内存中生成实现了指定接口的代理类字节码。
  3. 代理类(如 $Proxy0)继承自 Proxy 并实现目标接口,其内部持有一个 InvocationHandler 对象,所有接口方法的调用都会被重定向并转发到该处理器的 invoke 方法中执行。
相关推荐
衔烛之龙11 小时前
Windows x64 构建 liboqs-java教程
java·windows·python
KouFiee1 小时前
同名隐藏基础
开发语言·c++
一路向北North1 小时前
Spring Security OAuth2.0(20):完善环境配置
java·后端·spring
Java面试题总结1 小时前
IntelliJ IDEA 从卡顿到起飞,只用改这些。。。
java·ide·intellij-idea
z落落1 小时前
C# Task WaitAll、WaitAny、WhenAll、WhenAny
开发语言·c#
兰令水1 小时前
hot100【acm版】【2026.7.11/12打卡-java版本】
java·开发语言·数据结构·算法·职场和发展
从零开始的代码生活_1 小时前
C++ string 详解:常用接口、字符串算法与深拷贝实现
开发语言·c++·后端·学习·算法
Misnearch2 小时前
nacos配置管理改造
java·spring cloud·nacos
XWalnut2 小时前
LeetCode刷题 day29
java·算法·leetcode