Java对象竟能“分身有术“?JDK与CGLIB动态代理

当Java对象学会"影分身"

想象你是一名外卖小哥(真实对象),现在需要:

  1. 接单时自动记录订单信息(日志)
  2. 送餐前检查车辆状况(安全检查)
  3. 送达后自动发送通知(消息推送)

但公司要求不能修改你的核心送餐流程——这时就需要动态代理这个"分身术"!它能帮你自动生成一个"智能分身",在保持你原有工作方式的同时,悄悄加上这些新功能。

一、初识动态代理:外卖平台的智能调度系统

1.1 什么是动态代理?

动态代理就像外卖平台的智能调度系统:

  • 传统方式:手动为每个骑手配调度员(静态代理)
  • 智能系统:自动为所有骑生成虚拟调度员(动态代理)
// 传统静态代理:一对一服务
class TakeoutProxy extends TakeoutWorker {
    public void deliver() {
        System.out.println("[记录]开始配送");
        super.deliver();  // 调用真实对象
    }
}

// 动态代理:智能调度所有骑手
Object proxy = Proxy.newProxyInstance(...);

1.2 为什么要用动态代理?

  • 不改代码加功能:像给手机装APP,不拆机就能新增能力
  • 模块化开发:把日志、安全等"配件"拆分成独立模块
  • 动态适配:运行时才决定如何增强对象

二、JDK动态代理:接口契约下的"标准化分身"

2.1 工作原理图解

调用者
代理对象
InvocationHandler
真实对象
真实对象
+save()
代理对象
-InvocationHandler
+save()
InvocationHandler
+invoke()
接口

2.2 三大必备条件

  1. 必须有接口:就像外卖平台要求骑手注册时必须签协议
  2. 通过反射调用:像平台用APP远程指挥骑手
  3. Java原生支持:不用额外安装"插件"

2.3 代码实战:给外卖服务加日志

// 1.定义接口(平台协议)
interface TakeoutService {
    void deliverFood();
}

// 2.真实对象(骑手)
class Rider implements TakeoutService {
    public void deliverFood() {
        System.out.println("骑手正在送餐...");
    }
}

// 3.创建代理
TakeoutService proxy = (TakeoutService) Proxy.newProxyInstance(
    Rider.class.getClassLoader(),
    new Class[]{TakeoutService.class},
    (p, method, args) -> {
        System.out.println("[系统日志]开始执行" + method.getName());
        return method.invoke(new Rider(), args);
    }
);

proxy.deliverFood();
// 输出:
// [系统日志]开始执行deliverFood
// 骑手正在送餐...

三、CGLIB动态代理:无接口也能玩的"基因克隆术"

3.1 核心原理

继承
代理对象
真实对象
拦截器
真实对象
+save()
代理对象
-MethodInterceptor
+save()
MethodInterceptor
+intercept()

3.2 突出特点

  • 不需要接口:直接克隆真实对象的"基因"
  • 性能更高:像基因编辑直接修改,比反射快
  • ⚠️ 不能代理final类:就像无法克隆被锁死的DNA

3.3 代码实战:给任意类加缓存

// 1.引入依赖(基因编辑工具包)
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

// 2.创建增强器
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ProductDAO.class); // 指定要克隆的类
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    if (method.getName().equals("getById")) {
        System.out.println("[缓存]先查缓存...");
    }
    return proxy.invokeSuper(obj, args);
});

// 3.生成代理对象
ProductDAO proxy = (ProductDAO) enhancer.create();
proxy.getById(101);
// 输出:
// [缓存]先查缓存...
// 执行真实查询...

四、终极对决:JDK vs CGLIB 性能大比拼

4.1 对比表格(⭐越多越优秀)

特性 JDK动态代理 CGLIB动态代理
是否需要接口 ⭐⭐⭐(必须) ⭐⭐⭐(不需要)
执行速度 ⭐⭐ ⭐⭐⭐
创建代理速度 ⭐⭐⭐ ⭐⭐
内存消耗 ⭐⭐⭐ ⭐⭐
方法限制 仅接口方法 不能代理final方法

4.2 性能实测数据(调用100万次)

代理方式 耗时(ms)
直接调用 15
JDK动态代理 180
CGLIB代理 120

五、Spring框架的智能选择策略

Spring就像经验丰富的教练:

  1. 先检查有没有实现接口 → 有就用JDK代理
  2. 没有接口但有继承空间 → 用CGLIB
  3. 遇到final类 → 直接报错:“这届选手带不动!”
// Spring配置强制使用CGLIB
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {}

六、开发选型指南:什么时候用哪种?

6.1 选JDK动态代理当:

  • ✅ 对象已经实现接口
  • ✅ 追求代理创建速度
  • ✅ 不想引入额外依赖

6.2 选CGLIB当:

  • ✅ 对象没有接口
  • ✅ 需要极致执行性能
  • ✅ 可以接受初始加载稍慢

6.3 两个都不能用时:

  • ❌ 需要代理final类 → 考虑装饰器模式
  • ❌ 需要代理static方法 → 改用AspectJ

七、黑科技彩蛋:动态代理的骚操作

7.1 实现伪缓存

public Object invoke(Object proxy, Method method, Object[] args) {
    String key = args[0].toString();
    if (cache.containsKey(key)) {
        System.out.println("命中缓存!");
        return cache.get(key);
    }
    Object result = method.invoke(target, args);
    cache.put(key, result);
    return result;
}

7.2 方法调用限流

private RateLimiter limiter = RateLimiter.create(10.0); // 每秒10次

public Object invoke(Object proxy, Method method, Object[] args) {
    if (!limiter.tryAcquire()) {
        throw new RuntimeException("操作太频繁!");
    }
    return method.invoke(target, args);
}

结语:动态代理的哲学

就像《盗梦空间》中的造梦师,动态代理让我们能在运行时"植入"新功能。记住:

  • JDK代理是"契约式增强"(必须按接口办事)
  • CGLIB是"基因式改造"(直接继承修改)

思考题:如果要用动态代理实现自动重试机制(比如网络调用失败自动重试3次),代码该怎么写?欢迎在评论区秀出你的实现!

你可能感兴趣的:(Java基础,java,开发语言,后端)