动态代理核心在于运行时无缝织入增强逻辑,关键在机制选择与边界理清:jdk代理依赖接口和反射,cglib通过子类重写非final方法,spring依类结构自动选型,增强需覆盖异常与返回处理。
动态代理不是“写个接口再套层壳”那么简单,它是在运行时把增强逻辑无缝织入目标方法的执行链路中。关键不在代码怎么写,而在选对机制、避开限制、理清调用边界。
JDK动态代理:接口是硬门槛,反射是双刃剑
JDK代理只认接口——目标类必须实现至少一个接口,否则直接抛异常。它生成的是一个新类,这个类实现了你指定的所有接口,内部通过InvocationHandler统一拦截所有方法调用。
- 代理对象不能强转成目标类类型(比如
(UserServiceImpl) proxy会失败),只能转成接口类型 -
invoke方法里调用method.invoke(target, args)走的是Java反射,JDK 8之后性能尚可,但高频调用仍比直接调用慢一截 - 无法代理
private、static、final方法,连toString()这种继承自Object的方法,如果没在接口里声明,也不会被代理
CGLIB动态代理:绕过接口,但得让出继承权
CGLIB不看接口,只看类——它用ASM在内存中动态生成目标类的一个子类,重写所有非final的public方法,在方法体开头插入拦截器逻辑。
- 目标类不能是
final,方法也不能是final,否则子类无法覆盖,代理创建直接失败 - 必须用
MethodProxy.invokeSuper(obj, args)调用原方法;若误用method.invoke()或methodProxy.invoke(),会触发代理对象自身的方法,导致无限递归和栈溢出 - 首次创建代理类开销较大(字节码生成+类加载),但后续实例复用快;方法执行时走FastClass索引,比反射快
Spring里怎么选?不是配置决定的,是类结构说了算
Spring AOP默认策略很务实:有接口就走JDK代理,没接口才退到CGLIB。这不是性能偏好,而是兼容性选择。
- 哪怕你配了
@EnableAspectJAutoProxy(proxyTargetClass = true)强制启用CGLIB,如果目标类是final,Spring启动时就会报错 - 接口方法内部调用本类另一个接口方法(即this.xxx()),不会被代理——这是AOP的天然盲区,和代理类型无关
- 若一个Bean既实现了接口,又被标记为
@Scope("prototype"),每次获取都是新代理对象,注意拦截器状态是否可重用
增强逻辑落地:别只顾“前后”,要管住“异常”和“返回”
真实场景中,日志、监控、熔断这些功能,光打个时间戳远远不够。
- 前置逻辑里建议记录方法签名+参数摘要(避免打印敏感字段),而不是完整对象toString()
- 后置逻辑应区分正常返回与异常退出:
try...finally只保执行,try...catch才能捕获业务异常并做降级处理 - 若需修改返回值(如脱敏、补字段),必须在
intercept或invoke中return新对象;原方法返回值只是参考,不改就原样透传











