Java代理详解:JDK 动态代理执行流程
下面的流程讲解基于Java——代理
JDK 动态代理执行流程(以上面文章的代码为例代码为例):
1.创建代理对象
执行 new ZFU(new ZF()).aa() 时,Proxy.newProxyInstance() 动态生成一个实现 ZFF 接口的代理对象:
- 参数 1:复用目标类(ZF)的类加载器
- 参数 2:指定代理类需实现 ZFF 接口(因此可被 ZFF 类型接收)
- 参数 3:绑定当前 ZFU 对象(实现 InvocationHandler 接口,作为代理逻辑处理器)
2.调用代理对象方法
当执行 zff.zf("A", "B", 100.00) 时:
- 由于 zff 是动态生成的代理对象(实现 ZFF 接口),调用其 zf() 方法会被 JDK 拦截
3.转发至 invoke () 方法
JDK 将拦截到的调用转发给绑定的 InvocationHandler(即 ZFU 对象)的 invoke() 方法:
- method 参数自动传入被调用的方法(即 ZF 类的 zf() 方法)
- args 参数自动传入调用时的参数("A","B",100.00)
执行增强逻辑与目标方法
4.在 invoke() 方法中:
- 可添加自定义增强逻辑(如参数验证、日志等)
- 通过 method.invoke(o, args) 反射调用目标对象(ZF 实例)的真实 zf() 方法,执行核心业务逻辑(打印支付信息)
核心逻辑链:
代理对象方法调用 → JDK 拦截并转发 → InvocationHandler 的 invoke () → 执行增强逻辑 + 反射调用目标方法
注:
1.为什么调用 zff.zf () 会进入 invoke ()?
- 因为 Proxy.newProxyInstance() 方法的第三个参数传入了 this(当前 ZFU 对象,它实现了 InvocationHandler 接口),JDK 在动态生成代理对象时,会将这个 InvocationHandler 与代理对象 “绑定”。
- 当你调用代理对象的 zf() 方法时,JDK 底层会自动触发绑定好的 InvocationHandler(也就是你的 ZFU 对象)的 invoke() 方法,并把当前调用的方法(zf())、参数("A","B",100.00)等信息传递给 invoke() 方法。
- 这个绑定关系是 JDK 动态代理的核心机制 —— 通过传入 InvocationHandler,告诉代理对象 “当方法被调用时,该找谁处理”,从而实现了方法调用的转发。
2.代理类指的是Proxy.newProxyInstance() 动态生成的匿名类(由 JDK 在运行时创建),这个类才是实现了 ZFF 接口的 “代理类”,它会 “冒充” 目标接口的实现类,接收外部方法调用,并将调用转发给 ZFU 的 invoke() 方法。。ZFU 类不是代理类,它是 InvocationHandler 的实现类(代理逻辑处理器),负责定义 “方法被调用时要执行的增强逻辑”(比如参数验证、日志等),并通过 invoke() 方法接管代理对象的方法调用。
3.代理类的作用
- 实现目标接口
代理类会实现目标对象所实现的接口(如代码中的 ZFF 接口),因此可以被接口类型接收(如 ZFF zff = ...),从语法上 “冒充” 目标接口的实现类,让外部调用者可以像使用目标对象一样使用代理对象。
- 拦截接口方法调用
代理类会重写接口中的所有方法(如 zf() 方法)。当外部调用这些方法时,代理类不会直接执行目标对象的方法,而是将调用拦截并转发给绑定的 InvocationHandler(如代码中的 ZFU 对象)的 invoke() 方法。
- 传递调用信息
在转发过程中,代理类会将 “当前调用的方法(Method 对象)”“传入的参数(args 数组)” 等信息打包,作为参数传递给 invoke() 方法,让 InvocationHandler 可以知晓并处理具体的调用细节。
4.Proxy.newProxyInstance()第一个参数
在 Java 中,获取类加载器主要有两种常见方式:
- 通过类名获取:类名.class.getClassLoader()
例如 JdkPoxy.class.getClassLoader(),这里的 JdkPoxy.class 是获取该类的字节码对象,再通过 .getClassLoader() 得到加载这个类的类加载器。
- 通过对象实例获取:对象.getClass().getClassLoader()
例如 o.getClass().getClassLoader(),这里的 o 是某个类的实例,o.getClass() 会返回该实例对应的类的字节码对象,再获取其类加载器。
这两种方式的本质是一样的 —— 都是通过「类的字节码对象」获取类加载器,只是获取字节码对象的途径不同(一个直接通过类名,一个通过实例)。
在动态代理中,只要能拿到一个「有效的类加载器」即可,无论这个类加载器来自哪个类(只要它有能力加载相关的类)。所以实际开发中,这两种写法都很常见,根据代码上下文选择更方便的即可。
5.Proxy.newProxyInstance()第二个参数
第二个参数,是不是必须要实现接口,否则不能通过jdk的这种动态代理,只能使用其他的。因为JDK 动态代理的核心机制就是基于接口实现的,这是由其设计原理决定的:
- 第二个参数的作用:o.getClass().getInterfaces() 是获取目标对象所实现的所有接口。JDK 动态代理会基于这些接口动态生成一个代理类,这个代理类会实现与目标对象相同的接口。
- 必须实现接口的原因:
- JDK 动态代理生成的代理类本质上是 "实现了目标接口" 的新类
- 当你调用代理对象的方法时,实际是调用了接口中定义的方法
- 如果目标对象没有实现任何接口,getInterfaces()会返回空数组,此时 JDK 无法生成代理类,会抛出IllegalArgumentException异常
- 解决方案:
-
- 如果目标类有接口:可以正常使用 JDK 动态代理
- 如果目标类没有接口:必须使用其他代理方式,最常用的是CGLIB 代理(基于继承目标类实现)
简单说:JDK 动态代理是 "接口导向" 的,必须要有接口才能工作;没有接口时,就需要用 CGLIB 等基于继承的代理方式。
6.Proxy.newProxyInstance()第三个参数
Proxy.newProxyInstance() 方法的第三个参数确实需要传入一个实现了 InvocationHandler 接口的对象。这是 JDK 动态代理的核心设计,它的工作流程可以简单理解为:
- 当你通过代理对象(代理类的实例)调用任何方法时(这些方法都是目标接口中定义的)
- JDK 会自动拦截这个调用,不会直接执行目标对象的方法
- 转而执行你传入的 InvocationHandler 对象的 invoke() 方法
- 在 invoke() 方法内部,你可以:
- 添加增强逻辑(比如你的 YZNameA()、YZNameB() 等验证方法)
- 通过 method.invoke(o, args) 手动调用目标对象的原始方法
- 对返回结果进行处理
在你的 ZFU 类中,this 之所以可以作为第三个参数,正是因为 ZFU 类本身实现了 InvocationHandler 接口,所以 this 就是一个符合要求的 InvocationHandler 实现对象。
简单说:第三个参数是「拦截器」,所有对代理对象的方法调用都会被它拦截并处理,这也是动态代理能实现 "增强" 功能的关键所在。
