一句话总结
代理模式三种实现:静态代理(手动编写代理类,编译期确定,类膨胀)、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() 获取代理对象,或拆到不同类。