代理模式?

2025年 阅读约 12 分钟 面试指南 · 设计模式

深入解析代理模式三种实现:静态代理、JDK动态代理、CGLIB动态代理的原理与区别,Spring AOP中的代理选择策略,附面试模拟问答。

一句话总结

代理模式三种实现:静态代理(手动编写代理类,编译期确定,类膨胀)、JDK 动态代理(Proxy.newProxyInstance(),基于接口,运行时生成代理类)、CGLIB 动态代理(Enhancer,基于继承,运行时生成子类,不能代理 final 类/方法)。Spring AOP 默认策略:有接口用 JDK 动态代理,无接口用 CGLIB。Spring Boot 2.x 默认使用 CGLIB(proxyTargetClass=true)。

初级理解

静态代理

# 接口 public interface UserService { void save(); } # 目标对象 public class UserServiceImpl implements UserService { public void save() { System.out.println("保存用户"); } } # 代理对象(手动编写) public class UserServiceProxy implements UserService { private UserService target; public UserServiceProxy(UserService target) { this.target = target; } public void save() { System.out.println("开启事务"); // 前置增强 target.save(); // 调用目标方法 System.out.println("提交事务"); // 后置增强 } } # 使用 UserService proxy = new UserServiceProxy(new UserServiceImpl()); proxy.save(); # 缺点:每个接口都要写一个代理类,类膨胀

中级深入

JDK 动态代理

# JDK 动态代理核心:Proxy + InvocationHandler public class JdkProxy implements InvocationHandler { private Object target; public JdkProxy(Object target) { this.target = target; } // 获取代理对象 public Object getProxy() { return Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器 target.getClass().getInterfaces(), // 接口数组 this // InvocationHandler ); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置增强: " + method.getName()); Object result = method.invoke(target, args); // 反射调用 System.out.println("后置增强: " + method.getName()); return result; } } # 使用 UserService target = new UserServiceImpl(); UserService proxy = (UserService) new JdkProxy(target).getProxy(); proxy.save(); # 原理: # 1. Proxy.newProxyInstance() 运行时动态生成代理类字节码 # 2. 代理类实现所有传入的接口 # 3. 每个方法调用都转发到 InvocationHandler.invoke() # 4. 生成的代理类:$Proxy0 extends Proxy implements UserService # 限制:只能代理接口,不能代理类

CGLIB 动态代理

# CGLIB 核心:Enhancer + MethodInterceptor public class CglibProxy implements MethodInterceptor { private Object target; public CglibProxy(Object target) { this.target = target; } public Object getProxy() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); // 设置父类 enhancer.setCallback(this); // 设置回调 return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("前置增强"); // 方式1:反射调用 // Object result = method.invoke(target, args); // 方式2:CGLIB 快速调用(推荐,无反射) Object result = proxy.invokeSuper(obj, args); System.out.println("后置增强"); return result; } } # 原理: # 1. Enhancer.create() 运行时生成目标类的子类 # 2. 子类重写所有非 final 方法 # 3. 方法调用转发到 MethodInterceptor.intercept() # 4. 生成的代理类:UserServiceImpl$$EnhancerByCGLIB$$xxx # 限制: # 1. 不能代理 final 类(无法继承) # 2. 不能代理 final 方法(无法重写) # 3. 需要引入 cglib 依赖

高级进阶

JDK vs CGLIB 对比

对比维度JDK 动态代理CGLIB 动态代理
原理实现接口继承父类
代理对象类型$Proxy0 extends Proxy子类 extends 目标类
要求必须有接口不能是 final 类/方法
性能创建快,调用略慢(反射)创建慢,调用快(无反射)
依赖JDK 内置需引入 cglib

Spring AOP 代理选择

# Spring AOP 代理选择策略: # 1. 如果目标对象实现了接口 → 默认 JDK 动态代理 # 2. 如果目标对象没有实现接口 → CGLIB 动态代理 # 3. 可以强制使用 CGLIB:@EnableAspectJAutoProxy(proxyTargetClass = true) # Spring Boot 2.x 默认行为变化: # spring.aop.proxy-target-class=true(默认) # 即:即使有接口,也默认使用 CGLIB # 为什么 Spring Boot 默认用 CGLIB? # 1. 避免类型转换问题(JDK 代理只能转接口类型) # 2. 统一代理行为 # 3. CGLIB 性能已大幅优化 # @Transactional 失效场景(代理模式相关): # 1. 同类方法调用(this.method() 不走代理) # 2. 非 public 方法 # 3. 异常被 catch 吞掉 # 4. 方法不是从外部调用(内部调用不触发代理)

JDK 动态代理源码分析

# Proxy.newProxyInstance() 核心流程: # 1. getProxyClass0() → 从缓存获取或生成代理类 # 2. 代理类生成:ProxyGenerator.generateProxyClass() # 3. 生成字节码 → 加载到 JVM # 4. 反射获取构造方法 → 创建代理实例 # 生成的代理类结构(简化): public final class $Proxy0 extends Proxy implements UserService { private static Method m3; // save() 方法 static { m3 = Class.forName("UserService").getMethod("save"); } public $Proxy0(InvocationHandler h) { super(h); } public final void save() { super.h.invoke(this, m3, null); // 转发到 InvocationHandler } } # 为什么 JDK 代理比 CGLIB 创建快? # JDK:直接生成字节码,无需解析 class 结构 # CGLIB:需要解析目标类的所有方法,生成子类字节码

实战场景

# 场景1:MyBatis Mapper 代理(JDK 动态代理) UserMapper mapper = session.getMapper(UserMapper.class); # MapperProxy 实现 InvocationHandler # 调用 mapper.selectById(1) → MapperProxy.invoke() # → 解析方法名 → 执行 SQL → 返回结果 # 场景2:Spring AOP 日志切面 @Aspect @Component public class LogAspect { @Around("@annotation(Log)") public Object around(ProceedingJoinPoint pjp) throws Throwable { log.info("开始: {}.{}", pjp.getTarget().getClass(), pjp.getSignature().getName()); Object result = pjp.proceed(); log.info("结束: {}.{}", pjp.getTarget().getClass(), pjp.getSignature().getName()); return result; } } # 场景3:RPC 远程调用代理 # 客户端通过动态代理将本地接口调用转为网络请求 # 如 Feign、Dubbo 的 @Reference 注解

面试模拟

面试官:JDK 动态代理和 CGLIB 的区别?Spring 如何选择?

你:JDK 基于接口,通过 Proxy.newProxyInstance() 生成实现接口的代理类,创建快但调用需反射。CGLIB 基于继承,通过 Enhancer 生成目标类的子类,创建慢但调用无反射。Spring 默认:有接口用 JDK,无接口用 CGLIB。Spring Boot 2.x 默认用 CGLIB(proxyTargetClass=true),避免类型转换问题。

面试官:@Transactional 什么情况下会失效?

你:四种情况:1)同类方法调用(this.method() 不走代理);2)非 public 方法(AOP 只代理 public 方法);3)异常被 catch 吞掉(事务只回滚 RuntimeException 和 Error);4)方法不是从外部调用。解决方案:同类调用用 AopContext.currentProxy() 获取代理对象,或拆到不同类。