云计算百科
云计算领域专业知识百科平台

【关于Java的动态代理】

你有没有遇到过这样的需求?

👉 想给某个方法加个耗时统计,但不想改原有代码?
👉 想在每个业务方法执行前后打印日志,难道要每个方法都手动加 log.info()?
👉 面试官问:“Spring 的 AOP 是怎么实现的?” 你心里一紧,只憋出一句:“嗯……用了代理?”

带你彻底搞懂 Java 动态代理(Dynamic Proxy)!

它就像给对象装了个“影分身”,
你调用的是它,实际执行的却是另一个“幕后黑手”——
而且这一切,在运行时自动生成,不用你手写一行代理类!


一、什么是动态代理?—— 对象的“替身演员”

想象一下,明星(真实对象)很忙,不能亲自处理所有事。
于是他找了个经纪人(代理),对外说:“有事找我!”

粉丝(调用者)以为在跟明星打交道,
其实是经纪人在接电话、谈合同、安排行程……
必要时,经纪人才会去“通知”明星本人。

在 Java 世界里:

  • 明星 = 真实对象(Real Object)
  • 粉丝 = 调用者(Client)
  • 经纪人 = 代理对象(Proxy)

✅ 动态代理 = 在程序运行时,自动生成一个“代理类”,代替真实对象处理请求


二、Java 两种动态代理方式

1. JDK 动态代理 —— 基于接口(最常用)

要求:被代理的类必须实现至少一个接口。

🎯 核心三件套:
  • 接口(明星的“职业身份”)
  • 真实类(实现接口,明星本人)
  • InvocationHandler(经纪人,处理逻辑)

  • 💡 举个例子:给服务加日志和性能监控

    // 1. 定义接口
    public interface UserService {
    void saveUser(String name);
    String getUser(int id);
    }

    // 2. 真实实现类(明星本人)
    public class UserServiceImpl implements UserService {
    @Override
    public void saveUser(String name) {
    System.out.println("正在保存用户:" + name);
    // 模拟耗时
    try { Thread.sleep(100); } catch (InterruptedException e) {}
    }

    @Override
    public String getUser(int id) {
    System.out.println("正在查询用户:" + id);
    return "用户" + id;
    }
    }

    // 3. 经纪人(InvocationHandler)—— 核心!拦截所有方法调用
    public class LoggingInvocationHandler implements InvocationHandler {
    private Object target; // 真实对象

    public LoggingInvocationHandler(Object target) {
    this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 【前置增强】方法执行前
    String methodName = method.getName();
    System.out.println("【代理拦截】即将执行方法:" + methodName + ",参数:" + Arrays.toString(args));

    long start = System.currentTimeMillis();

    // 【核心】调用真实对象的方法
    Object result = method.invoke(target, args);

    // 【后置增强】方法执行后
    long cost = System.currentTimeMillis() – start;
    System.out.println("【代理拦截】方法 " + methodName + " 执行完成,耗时:" + cost + "ms,返回值:" + result);

    return result;
    }
    }

    // 4. 客户端使用(粉丝找明星)
    public class Client {
    public static void main(String[] args) {
    // 真实对象
    UserService realService = new UserServiceImpl();

    // 创建代理对象(经纪人上岗!)
    UserService proxyService = (UserService) Proxy.newProxyInstance(
    realService.getClass().getClassLoader(), // 类加载器
    realService.getClass().getInterfaces(), // 代理哪些接口
    new LoggingInvocationHandler(realService) // 代理逻辑
    );

    // 调用者以为在调真实对象,其实是代理在处理
    proxyService.saveUser("张三");
    proxyService.getUser(1001);
    }
    }

    ✅ 输出结果:

    【代理拦截】即将执行方法:saveUser,参数:[张三]
    正在保存用户:张三
    【代理拦截】方法 saveUser 执行完成,耗时:105ms,返回值:null

    【代理拦截】即将执行方法:getUser,参数:[1001]
    正在查询用户:1001
    【代理拦截】方法 getUser 执行完成,耗时:2ms,返回值:用户1001

    💡 看!我们没改一行 UserServiceImpl 的代码,就实现了日志和性能监控!


    2. CGLIB 动态代理 —— 基于继承(无接口也能代理)

    问题:如果类没实现接口,JDK 代理就歇菜了。

    解决方案:CGLIB(Code Generation Library)
    它通过继承真实类,生成子类作为代理。

    ✅ 优点:
    • 不需要接口,直接代理普通类。
    • Spring 默认用它处理没有接口的 Bean。
    ❌ 缺点:
    • 不能代理 final 类或 final 方法(无法继承或重写)。
    • 需要引入额外依赖(Spring 已内置)。
    💡 简单示例(Spring 中常见):

    // 一个没有接口的类
    public class OrderService {
    public void createOrder(String orderId) {
    System.out.println("创建订单:" + orderId);
    }
    }

    Spring AOP 会生成一个代理类,像这样:

    // 伪代码:CGLIB 生成的代理类
    public class OrderService$$EnhancerByCGLIB extends OrderService {
    private MethodInterceptor interceptor;

    @Override
    public void createOrder(String orderId) {
    // 调用拦截器(类似 InvocationHandler)
    interceptor.intercept(this, 方法对象, 参数, 原方法引用);
    }
    }


    三、动态代理 vs 静态代理 —— 手工打造 vs 工厂量产

    静态代理:每个接口都要写一个代理类,累死!

    public class UserServiceProxy implements UserService {
    private UserService target;

    public UserServiceProxy(UserService target) {
    this.target = target;
    }

    @Override
    public void saveUser(String name) {
    System.out.println("前置日志");
    target.saveUser(name);
    System.out.println("后置日志");
    }

    // getUser 也要写一遍……重复代码!
    }

    动态代理:一个 InvocationHandler 通吃所有接口!

    ✅ 动态代理 = 高度复用,无需提前写代理类,运行时自动生成


    四、高频问题 & 高分回答

    Q1: 动态代理的两种方式有什么区别?

    区别JDK 动态代理CGLIB 动态代理
    实现机制 基于接口,生成实现类 基于继承,生成子类
    是否需要接口 必须有 不需要
    代理 final 类 不行 不行
    性能 JDK 1.8+ 已优化,性能好 略快(直接调用)
    Spring 默认策略 有接口用 JDK 无接口用 CGLIB

    💡 加分点:
    “Spring AOP 默认优先用 JDK 动态代理(基于接口),更安全;如果没有接口,则用 CGLIB。”


    Q2: Proxy.newProxyInstance 返回的是什么对象?

    答:
    它返回一个实现了指定接口的代理类实例。
    这个类是运行时动态生成的,名字类似 com.sun.proxy.$Proxy0。
    它持有 InvocationHandler 的引用,所有方法调用都会被转发到 invoke() 方法中。


    Q3: 动态代理在实际项目中用在哪?

    答:

  • Spring AOP:实现日志、事务、权限校验等横切关注点;
  • RPC 框架:如 Dubbo,客户端用代理透明调用远程服务;
  • 懒加载:Hibernate 用代理实现关联对象的延迟加载;
  • Mock 测试:如 Mockito 用代理生成 Mock 对象。
  • 💬 我在项目中用 AOP + 动态代理统一处理接口耗时监控和操作日志,减少了大量重复代码。


    Q4: 为什么 JDK 动态代理必须基于接口?

    答:
    因为生成的代理类需要“伪装”成真实对象。
    Java 是单继承,如果用继承方式,就不能再继承其他类了。
    而接口可以实现多个,所以 JDK 选择“实现接口”来生成代理类,避免继承冲突,更灵活。


    ✅ 总结:一张表搞懂核心要点

    问题答案
    什么是动态代理 运行时自动生成代理类,拦截方法调用
    JDK 代理要求 被代理类必须实现接口
    核心接口 InvocationHandler + Proxy.newProxyInstance
    CGLIB 特点 基于继承,无需接口,但不能代理 final 类
    实际应用场景 Spring AOP、RPC、事务管理、日志监控
    面试怎么说 “我用 AOP 做了统一日志和性能监控,底层就是动态代理”

    🔚 最后一句话

    动态代理不是“黑魔法”,而是“解耦利器”。
    它让你把核心业务和横切逻辑(日志、事务、安全)彻底分开,
    实现“代码零侵入”,这才是 AOP 的真正魅力!

    希望这篇能帮你彻底搞懂 Java 动态代理!

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【关于Java的动态代理】
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!