当前位置: 首页 > java >正文

JDK 动态代理: 它的工作原理是什么?它有什么限制?

核心定义

JDK 动态代理是 Java 官方提供的一种在运行时动态创建代理对象的技术。它允许我们创建一个“替身”对象,这个替身可以在不修改原始业务代码的情况下,拦截对原始对象方法的调用,并在调用前后插入自定义的逻辑(即 AOP 中的“通知”)。


工作原理

JDK 动态代理的核心是两个组件:java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口。

它的工作流程可以分解为以下几个步骤:

1. 前提:定义接口和目标类
首先,你必须有一个接口,以及一个实现了该接口的目标类(也就是你想要代理的原始业务类)。

// 1. 定义接口
public interface SmsService {String send(String message);
}// 2. 目标类,实现接口
public class SmsServiceImpl implements SmsService {@Overridepublic String send(String message) {System.out.println("【核心业务】: 正在发送短信: " + message);return "短信发送成功";}
}

2. 编写处理器 (InvocationHandler)
你需要创建一个实现了 InvocationHandler 接口的类。这个类是代理的灵魂,所有对代理对象方法的调用最终都会被转发到它的 invoke 方法里。你所有的增强逻辑(比如日志、计时、权限检查)都写在这里。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;// 3. 编写处理器,这是所有增强逻辑的所在地
public class MyInvocationHandler implements InvocationHandler {// 持有对目标对象的引用private final Object target;public MyInvocationHandler(Object target) {this.target = target;}/*** 当你调用代理对象的任何方法时,这个 invoke 方法就会被执行。* @param proxy   动态生成的代理对象本身 (很少使用)* @param method  被调用的目标方法 (例如 send 方法)* @param args    调用目标方法时传入的参数* @return        目标方法的返回值*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// --- 前置增强 ---System.out.println("【前置通知】: 方法 " + method.getName() + " 即将执行...");// 通过反射调用原始目标对象的方法Object result = method.invoke(target, args);// --- 后置增强 ---System.out.println("【后置通知】: 方法 " + method.getName() + " 执行完毕。");return result; // 返回原始方法的执行结果}
}

3. 生成并使用代理对象
最后,使用 Proxy.newProxyInstance() 这个静态方法来动态地创建代理对象。

public class JdkProxyFactory {public static Object getProxy(Object target) {return Proxy.newProxyInstance(// 1. 目标对象的类加载器 (ClassLoader)//    用于加载动态生成的代理类。target.getClass().getClassLoader(),// 2. 目标对象实现的所有接口 (Class<?>[])//    代理对象会实现这些接口,告诉代理它需要有哪些方法。target.getClass().getInterfaces(),// 3. 我们编写的处理器 (InvocationHandler)//    将方法调用分派到这里。new MyInvocationHandler(target));}
}// --- 客户端使用 ---
public class Main {public static void main(String[] args) {// 创建目标对象SmsService target = new SmsServiceImpl();// 获取代理对象SmsService proxy = (SmsService) JdkProxyFactory.getProxy(target);// 通过代理对象调用方法String response = proxy.send("Hello, JDK Proxy!");System.out.println("客户端收到响应: " + response);// 代理对象的真实类型是什么?System.out.println("代理对象的类型: " + proxy.getClass().getName());}
}

运行结果:

【前置通知】: 方法 send 即将执行...
【核心业务】: 正在发送短信: Hello, JDK Proxy!
【后置通知】: 方法 send 执行完毕。
客户端收到响应: 短信发送成功
代理对象的类型: com.sun.proxy.$Proxy0

从结果可以看出,我们调用 proxy.send() 时,MyInvocationHandler 中的前后置逻辑被成功执行了。并且代理对象的类型是一个由 JVM 在运行时动态生成的、名字类似 $Proxy0 的类。


限制与缺点

JDK 动态代理虽然强大,但它有一个非常重要的核心限制:

  1. 必须基于接口进行代理 (Most Important Limitation)

    • 它只能代理实现了接口的类。 这是因为 Proxy.newProxyInstance() 的第二个参数要求传入一个接口数组。生成的代理类会实现这些接口,但它不会继承你的目标类。
    • 因此,如果一个类没有实现任何接口(一个普通的 POJO),JDK 动态代理就无法为它创建代理。这是它最大的局限性,也是为什么 Spring AOP 需要引入 CGLIB 作为补充的原因。
  2. 性能开销

    • 由于其底层是基于 Java 反射 (Reflection) 机制(即 method.invoke(...)),相比直接调用,会存在一定的性能开销。不过,在现代的 JVM 中,反射的性能已经得到了极大的优化,对于绝大多数业务场景来说,这点开销可以忽略不计。
  3. 类型转换问题

    • 代理对象 proxy 的类型是 com.sun.proxy.$Proxy0,它只实现了 SmsService 接口。你不能将它强制类型转换为 SmsServiceImpl
    • proxy instanceof SmsService 会返回 true
    • proxy instanceof SmsServiceImpl 会返回 false
    • 这在某些需要具体实现类类型的场景下可能会导致 ClassCastException

总结

特性描述
核心技术Java 反射 (java.lang.reflect.Proxy, InvocationHandler)
代理方式在运行时动态地实现目标对象的所有接口,生成代理类。
最大限制目标对象必须实现接口。无法代理没有接口的普通类。
优点Java 原生支持,无需引入任何第三方库。
缺点必须有接口;存在一定的反射性能开销;无法强转为具体实现类。
http://www.xdnf.cn/news/14583.html

相关文章:

  • Linux系统基本操作指令
  • 深度实战|星环OS三大创新场景解密:如何用确定性技术重构智能汽车安全与体验?
  • K8s入门指南:架构解析浓缩版与服务间调用实战演示
  • 【51单片机定时中断 工作方式0, 1ms 初值是如何计算的?】2022-5-13
  • vue3 el-table 行颜色根据 字段改变
  • 【论文阅读 | CVPR 2024 |Fusion-Mamba :用于跨模态目标检测】
  • Python打卡训练营Day56
  • 【单调栈】-----【Largest Rectangle in a Histogram】
  • AWS VPC 子网划分实战指南:从基础到进阶
  • 人人都是音乐家?腾讯开源音乐生成大模型SongGeneration
  • 人工智能学习51-ResNet训练
  • 【51单片机2位数码管100毫秒的9.9秒表】2022-5-16
  • 【转】如何画好架构图:架构思维的三大底层逻辑
  • 大数据时代的“广告魔法”:精准投放到底怎么玩?
  • 软件工程概述:核心概念、模型与方法全解析
  • 58-Oracle Autotrace功能和演进
  • Python新春烟花
  • 江科大STM32入门:DMA传输数据
  • CNN工作原理和架构
  • 【基础算法】贪心 (一) :简单贪心
  • Input事件处理引起卡顿
  • vue3+arcgisAPI4案例:智慧林业资源监测分析平台(附源码下载)
  • 55-Oracle-EXPLAIN PLAN(含23ai )实操
  • 终端里的AI黑魔法:OpenCode深度体验与架构揭秘
  • 启动hardhat 项目,下载依赖的npm问题
  • Taro 跨端应用性能优化全攻略:从原理到实践
  • 【设计模式】6.原型模式
  • FTTR+软路由网络拓扑方案
  • NY339NY341美光固态闪存NW841NW843
  • Flutter ListTile 深度解析