面试知识库
极高 进阶

代理模式#

一句话答案#

代理模式为目标对象提供替身控制访问,JDK 动态代理基于接口,CGLIB 基于继承生成子类,Spring AOP 的核心。

核心要点

代理模式为目标对象提供一个替身,以控制对目标对象的访问。Java 中有三种实现方式:

1. 静态代理

手动编写代理类,实现与目标类相同的接口:

  • 缺点:每个接口都要写一个代理类,代码膨胀

2. JDK 动态代理(基于接口)

运行时通过反射生成代理类,目标类必须实现接口

3. CGLIB 动态代理(基于继承)

通过字节码技术生成目标类的子类作为代理,不要求目标类实现接口:

三种方式对比:

维度静态代理JDK 动态代理CGLIB
实现方式手写代理类Proxy + InvocationHandler字节码生成子类
要求实现相同接口目标类必须有接口目标类不能是 final
性能编译期确定,最快反射调用,较慢字节码直接调用,略快于 JDK
Spring 选择有接口时默认使用无接口时使用(Spring Boot 2.x+ 默认 CGLIB)

Spring AOP 的代理选择逻辑:

  • 目标类实现了接口 → 默认 JDK 动态代理
  • 目标类没有实现接口 → 使用 CGLIB
  • Spring Boot 2.x 之后默认都使用 CGLIB(spring.aop.proxy-target-class=true

→ 详见 Spring-AOP原理

面试回答(2分钟版)

代理模式的核心是为目标对象提供一个替身来控制访问,在不修改原始代码的前提下做增强。Java中有三种实现:静态代理手动编写代理类实现相同接口,简单但每个接口都要写一个代理类,代码膨胀严重。JDK动态代理通过Proxy.newProxyInstance在运行时生成代理类,基于InvocationHandler的invoke方法做增强,但要求目标类必须实现接口,因为生成的代理类已经继承了Proxy类,Java单继承限制决定了只能通过接口来代理。CGLIB通过ASM字节码技术生成目标类的子类做代理,不要求接口,但不能代理final类和final方法因为子类无法重写。Spring AOP的代理选择逻辑是有接口默认用JDK代理,没接口用CGLIB,不过Spring Boot 2.x之后默认统一用CGLIB,可以通过spring.aop.proxy-target-class配置。日常用到的@Transactional、@Async、@Cacheable底层都是代理模式在发挥作用。

追问与易错

追问方向:

  • “JDK 动态代理为什么必须有接口?”→ 生成的代理类已经继承了 java.lang.reflect.Proxy,Java 单继承限制决定了只能通过实现接口来代理目标类的方法
  • “CGLIB 为什么不能代理 final 方法?”→ CGLIB 通过 ASM 字节码生成目标类的子类来实现代理,final 方法不可被子类重写,因此无法织入增强逻辑
  • “Spring AOP 默认用哪种代理?”→ 有接口默认 JDK 动态代理,无接口用 CGLIB;但 Spring Boot 2.x 起默认 proxy-target-class=true 统一使用 CGLIB

易错点:

  • ❌ “代理模式和装饰器模式一样”——代理控制访问,装饰器增强功能
  • ❌ “JDK 代理比 CGLIB 慢”——JDK8+ 两者性能差距很小