第一部分:代理模式的哲学——为什么要“多此一举”?

你必须理解代理模式的核心思想:控制访问与功能增强。

1. 核心定义

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

  • 真实对象 (Real Subject):最终要执行业务逻辑的对象。

  • 代理对象 (Proxy):持有了真实对象的引用,负责预处理消息、过滤消息、把消息转发给真实对象,以及事后处理消息。

2. 为什么要用代理?

在分布式或复杂业务系统中,直接访问真实对象会有风险或不便。代理可以实现:

  • 解耦:客户端不直接依赖真实对象。

  • 开闭原则 (OCP):无需修改原始代码,即可增加日志、事务、限流、权限校验等功能。


第二部分:静态代理——虽笨重但清晰

静态代理是由程序员创建或工具生成源代码,再进行编译。在程序运行前,代理类的 .class 文件就已经存在了。

1. 代码实战

Java

// 1. 定义接口
public interface SmsService {
    String send(String message);
}
​
// 2. 真实实现类
public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("发送短信:" + message);
        return message;
    }
}
​
// 3. 代理类
public class SmsProxy implements SmsService {
    private final SmsService smsService; // 持有真实对象
​
    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }
​
    @Override
    public String send(String message) {
        System.out.println("方法执行前:记录日志");
        smsService.send(message); // 调用真实对象
        System.out.println("方法执行后:清理现场");
        return message;
    }
}

2. 局限性

  • 类爆炸:一个接口就要对应一个代理类,成百上千个接口会疯掉。

  • 维护难:接口一旦增加方法,真实类和代理类都得改。


第三部分:JDK 动态代理——运行时的黑魔法

面试官:“JDK 动态代理为什么必须要求实现接口?”

深度回答:因为 JDK 生成的代理类(如 $Proxy0)已经继承了 java.lang.reflect.Proxy。Java 是单继承的,所以它只能通过实现接口来完成代理。

1. 核心机制

它利用 java.lang.reflect 包下的 Proxy 类和 InvocationHandler 接口。

2. 代码实战

Java

public class DebugInvocationHandler implements InvocationHandler {
    private final Object target; // 被代理的目标对象
​
    public DebugInvocationHandler(Object target) {
        this.target = target;
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = method.invoke(target, args); // 反射调用
        System.out.println("After method: " + method.getName());
        return result;
    }
}
​
// 使用方式
SmsService smsService = (SmsService) Proxy.newProxyInstance(
    SmsServiceImpl.class.getClassLoader(),
    new Class[]{SmsService.class},
    new DebugInvocationHandler(new SmsServiceImpl())
);

第四部分:CGLIB 动态代理——暴力美学

面试官:“如果类没有实现接口,还能代理吗?”

回答:当然,用 CGLIB。它的原理是动态生成一个子类去继承被代理类

1. 核心特点

  • 原理:通过 ASM 字节码操作框架生成子类。

  • 限制:不能代理 final 类和 final/private 方法(因为无法被子类重写)。

2. 代码实战

Java

public class DebugMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CGLIB 增强前...");
        Object result = proxy.invokeSuper(obj, args); // 调用父类(真实对象)方法
        System.out.println("CGLIB 增强后...");
        return result;
    }
}

第五部分:面试复盘脑图

为了帮你将两万字的知识点内化,我整理了这张核心脑图:

Code snippet

mindmap
  root((Java 代理模式))
    核心价值
      解耦与扩展
      无侵入式增强 (日志/事务/权限)
      Spring AOP 的基石
    静态代理
      编译期确定
      类爆炸问题, 维护成本高
    JDK 动态代理
      运行时生成字节码
      依赖 java.lang.reflect.Proxy
      必须实现接口 (单继承限制)
      反射调用, 效率随版本提升已接近原生
    CGLIB 动态代理
      继承机制: 生成子类
      不要求接口
      无法代理 final 类/方法
      字节码技术实现 (ASM)
    实战对比
      JDK 更稳定, 官方支持
      CGLIB 更灵活, 适合无接口场景
      Spring AOP: 默认有接口用JDK, 无接口用CGLIB

第六部分:大厂面试官的“深度思考题”

  1. Spring AOP 默认使用的是哪种代理?

    • 回答要点:如果目标对象实现了接口,默认用 JDK;如果没有实现接口,强制用 CGLIB。当然,你也可以通过配置 proxy-target-class=true 强制全量使用 CGLIB。

  2. 动态代理的性能真的比静态代理差吗?

    • 回答要点:早期 JDK 确实差。但从 JDK 8 开始,JDK 动态代理的效率有了质的飞跃。在现代 JVM 中,反射调用的开销非常小,且由于减少了大量的类加载,动态代理在整体系统内存占用上更具优势。

  3. 代理模式和装饰器模式(Decorator)有什么区别?

    • 回答要点

      • 装饰器模式:侧重于“增强”,你明确知道你在装饰谁。

      • 代理模式:侧重于“控制”,你可能根本不知道后台真实的执行者是谁(如远程代理 RPC)。


结语:从“理解代码”到“理解框架”

代理模式是 Java 框架“灵活性”的源泉。 掌握了代理,你就能读懂 Spring 的事务回滚逻辑,就能理解 MyBatis 是如何把一个接口变成 SQL 执行引擎的。

这篇文章梳理了代理模式的所有命门。如果你在面试中能手写出 JDK 动态代理的流程并分析出它和 CGLIB 的字节码差异,大厂的入场券已经在你手里了。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐