Java动态代理机制详解

动态代理

一、从现实生活开始:一个完美的比喻

想象一个场景:你想邀请一个明星(我们叫他 “周杰伦”)来你的生日派对唱歌。

  1. 你遇到的问题
    • 你没有周杰伦的直接联系方式。
    • 即使有,他本人也很忙,不会直接处理这种预约请求(谈合同、安排时间、讨论曲目等)。
    • 你不想,或者无法直接和他沟通这些琐事。
  2. 解决方案: 你该怎么办呢?你会找到他的​​经纪人​​。
  3. 经纪人做了什么
    • 你直接和经纪人沟通。
    • 经纪人代表周杰伦,和你谈合同、费用、曲目等事宜。
    • 对于你提出的要求(比如“唱《晴天》”),经纪人可能会做一些额外的工作事前:记录你的需求,可能还会帮你看看预算够不够(增强你的请求)。 事后:告诉你没问题,并且安排档期(处理结果)。
    • 最后,在派对上,经纪人确保周杰伦出场并完成了唱歌这个核心动作

在这个比喻中:

  • 周杰伦 = 真实对象:他有核心能力(唱歌),但不直接对外服务。
  • 经纪人 = 代理对象:他代表真实对象,对外提供服务,并且可以在执行核心动作前后做一些额外的事情。
  • = 客户端:你只想达成目的(听到歌),不关心是直接还是间接通过谁完成的。

二、Java 代理的核心思想

Java 代理就是编程世界里的“经纪人”。它的核心思想是:为一个对象(真实对象)提供一个代理(代理对象),以控制对这个对象的访问,并且可以在执行真实对象的方法前后加入自定义的逻辑。

为什么要这么做?

  • 功能增强:在不修改原始类代码的前提下,为方法添加日志、性能统计、事务管理、权限校验等“额外功能”。这符合著名的 AOP(面向切面编程) 思想。
  • 控制访问:延迟加载(比如大型图片、文件,直到真正需要时才创建真实对象)、权限控制(检查是否有权调用某方法)。
  • 简化客户端调用:客户端可能只需要一个统一的接口,而不用关心背后真实对象的复杂实现或生命周期。

三、Java 中两种主要的代理模式

Java 主要提供了两种方式来创建代理:“静态代理”和“动态代理”。动态代理又分为基于 JDK 的和基于第三方库(如 CGLIB)的。

1. 静态代理

特点:代理类在程序运行前就已经手动编写、编译好了。.java 文件是实实在在存在的。

如何实现

  1. 定义一个接口(规定明星能做什么,比如 Singer 接口,里面有 sing() 方法)。
  2. 一个真实对象(周杰伦 JayChou 类)实现这个接口。
  3. 一个代理类(经纪人 SingerAgent 类)也实现这个接口。
  4. 代理类内部持有一个真实对象的引用(经纪人认识周杰伦)。
  5. 代理类实现接口方法时,调用真实对象的方法,并在其前后添加额外逻辑。

代码示例

// 1. 接口
public interface Singer {
    void sing();
}

// 2. 真实对象
public class JayChou implements Singer {
    @Override
    public void sing() {
        System.out.println("周杰伦演唱《晴天》");
    }
}

// 3. 代理类
public class SingerAgent implements Singer { // 代理也要实现相同接口
    private Singer singer; // 持有真实对象的引用

    public SingerAgent(Singer singer) {
        this.singer = singer;
    }

    @Override
    public void sing() {
        System.out.println("经纪人:签订合同,安排档期..."); // 前置增强
        singer.sing(); // 调用真实对象的核心方法
        System.out.println("经纪人:结算费用,清理现场..."); // 后置增强
    }
}

// 4. 客户端使用
public class Client {
    public static void main(String[] args) {
        Singer jay = new JayChou(); // 创建真实对象
        Singer agent = new SingerAgent(jay); // 创建代理对象,传入真实对象
        agent.sing(); // 客户端调用的是代理对象的方法!
    }
}

输出

经纪人:签订合同,安排档期...
周杰伦演唱《晴天》
经纪人:结算费用,清理现场...

缺点:非常不灵活。如果接口增加了新方法,真实对象和代理对象都需要修改。一个代理类只能代理一种类型的对象。


2. 动态代理

特点:代理类是在程序运行时,通过反射机制动态生成的。我们不需要手动编写代理类的 .java 源文件。大大提高了灵活性。

Java 自身提供了基于接口的动态代理机制,主要使用 java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

如何实现

  1. 同样需要一个接口和一个真实对象
  2. 定义一个 InvocationHandler 实现类(调用处理器)。你可以把它理解为“代理行为的模板”或“经纪人的工作手册”。它只有一个 invoke 方法,所有对代理对象的方法调用都会转到这里。
  3. 使用 Proxy.newProxyInstance() 方法,动态地在内存中创建一个代理实例。

代码示例

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 1. 接口和真实对象(和上面静态代理一样)
public interface Singer { void sing(); }
public class JayChou implements Singer {
    @Override
    public void sing() { System.out.println("周杰伦演唱《晴天》"); }
}

// 2. 实现 InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
    private Object target; // 目标对象/真实对象,这里用 Object,更通用

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

    // 所有对代理对象的方法调用,都会转发到这个方法来处理
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("经纪人:签订合同,安排档期...");
        // 通过反射,调用真实对象的方法
        Object result = method.invoke(target, args);
        System.out.println("经纪人:结算费用,清理现场...");
        return result;
    }
}

// 3. 客户端使用
public class Client {
    public static void main(String[] args) {
        // 创建真实对象
        Singer jay = new JayChou();
        // 创建 InvocationHandler,传入真实对象
        InvocationHandler handler = new MyInvocationHandler(jay);
        
        // 动态创建代理对象!
        Singer proxy = (Singer) Proxy.newProxyInstance(
                jay.getClass().getClassLoader(), // 使用真实对象的类加载器
                jay.getClass().getInterfaces(),  // 代理对象需要实现的接口
                handler);                        // 调用处理器

        // 调用代理对象的方法
        proxy.sing();
    }
}

输出(和静态代理一样):

经纪人:签订合同,安排档期...
周杰伦演唱《晴天》
经纪人:结算费用,清理现场...

巨大优势

  • 非常灵活:这个 MyInvocationHandler 是通用的!它可以代理任何实现了接口的对象Singer, Actor, Player...),成为一个“万能经纪人”。如果要代理新类型,只需创建新的真实对象和接口,无需编写新的代理类。
  • 解耦:将“代理行为” (InvocationHandler) 和“被代理对象”完全分离开。

JDK 动态代理的限制:它只能代理实现了接口的类。如果一个类没有实现任何接口,JDK 的 Proxy 就无能为力了。

解决方案:CGLIB 动态代理

  • 第三方库 CGLIB 可以解决这个限制。它通过继承的方式动态创建子类来作为代理。
  • 它会重写父类(被代理类)的所有方法,并在子类中织入增强逻辑。
  • 因为是通过继承,所以无法代理 final 类或 final 方法。
  • Spring AOP 等框架默认会优先使用 JDK 动态代理,如果目标对象没有接口,则会自动切换到 CGLIB。

总结

特性 静态代理 动态代理 (JDK)
实现方式 手动编写、编译代理类 运行时通过反射动态生成代理类
灵活性 低。一个代理类代理一种类型 高。一个 InvocationHandler 可代理多种类型
性能 略好(直接编译) 略差(反射调用),但现代 JVM 对其优化很好
要求 需要接口 必须有接口
应用场景 早期简单应用,或明确知道代理类数量的情况 几乎所有现代框架(Spring AOP, RPC, 测试框架等)
← Java类加载机制详解 Java反序列化1 →