JDK 动态代理基于接口:运行时通过 Proxy.newProxyInstance 生成实现目标接口的代理类,所有方法调用转发到 InvocationHandler.invoke,利用反射分发给横切逻辑。要求目标必须实现接口。CGLIB 基于继承:用 ASM 字节码框架生成目标类的子类,重写父类方法织入增强,通过 MethodInterceptor 拦截。不要求接口,但无法代理 final 类和 final 方法(子类化不了),且构造器会被调用两次(早期版本)等继承固有代价。

Spring 的选择逻辑(AopProxyFactory):目标实现了接口 → 默认 JDK 代理;没有接口 → CGLIB;设了 proxyTargetClass=true(Spring Boot 2.x 起默认 true)→ 强制 CGLIB。注意 Boot 默认改 CGLIB 的原因:JDK 代理生成的对象只能用接口类型接收,按实现类 @Autowired 会注入失败,强制 CGLIB 消除了这类困惑。性能上现代 JDK 反射优化后两者差距不大,选择主要看语义约束而非性能。追问方向:代理导致 @Transactional 自调用失效的原理(内部调用不经过代理对象)、Java 16 起对 JDK 动态代理的模块封装影响、ByteBuddy 与 CGLIB 的对比(Mockito 等测试框架的选择)。

(约 430 字)