Java 面试题

Spring AOP原理:JDK动态代理 vs CGLIB

"Spring AOP是怎么实现的?什么时候用JDK动态代理什么时候用CGLIB?"

为什么面试官会问这道题

AOP实现是Spring面试IOC之后的经典追问。考察你对代理模式和字节码层差异的理解——直接关系到生产里"同类自调用@Transactional失效"这个常见坑。

如何回答

  1. 1

    核心:AOP生成一个包裹真实bean的代理对象,方法调用先跑advice(before/after/around)再委托给真实方法。

  2. 2

    JDK动态代理:只能代理接口。Proxy.newProxyInstance运行时生成实现目标接口的类,方法走InvocationHandler.invoke。

  3. 3

    CGLIB:运行时生成目标类的子类字节码。能代理普通类(不实现接口也行)。但final类和final方法代理不了。

  4. 4

    Spring规则:目标实现至少一个接口就用JDK代理,否则用CGLIB。Spring Boot 2.x+默认全用CGLIB(proxyTargetClass=true)。

  5. 5

    关键坑:AOP只对走代理的调用生效。同类内this.methodB()直接调原对象,不经过代理,advice不触发——@Transactional自调用失效就是这个原因。

参考回答示例

Spring AOP生成一个代理对象包裹你的bean,外部调用方持有代理,每次方法调用先跑advice链(before/after/around)再委托真实方法。两种代理机制。JDK动态代理用java.lang.reflect.Proxy运行时生成实现目标接口的类,方法调用走InvocationHandler的invoke()——里面判断方法名、应用advice。只能在目标实现了接口时用。CGLIB用字节码操作(底层ASM)运行时生成目标类的子类。方法被子类override,调用进子类先应用advice再super调父类。能代理普通类,但final类不能继承、final/private方法不能override。Spring默认规则是"有接口用JDK代理,没接口用CGLIB"。Spring Boot 2.x翻转了默认值——全用CGLIB(proxyTargetClass=true),避免调用方强转成具体类时的意外。生产最关键的坑是自调用失效。Foo类有方法a()和b(),b()加了@Transactional。a()里写this.b(),调的是原始Foo对象的b()方法,没经过代理,事务advice根本不跑。解决:把bean注入自己;或者用AopContext.currentProxy()拿到代理;或者重构把@Transactional方法挪到另一个bean里调。

实用技巧

  • AOP一定要带出"自调用失效"这个生产头号坑。

  • 知道Spring Boot 2.x+默认CGLIB,"有接口就JDK"是老知识。

  • 提到final类/final方法限制是字节码层理解的信号。

  • 忘了proxyTargetClass这个配置,即答侠可以实时提示。

常见问题

CGLIB为什么不能代理final类?

CGLIB靠继承,final类不能被继承,CGLIB会抛异常。final方法同理不能override。

两种代理性能差多少?

CGLIB调用更快(直接override),生成慢。JDK是反射。稳态运行差异忽略不计。

怎么看用的哪种代理?

bean.getClass().getName()——CGLIB带$$EnhancerBySpringCGLIB后缀,JDK代理是$Proxy数字。

面试时担心忘词?即答侠实时助你

即答侠 AI 实时监听面试对话,自动识别问题并即时生成回答建议——无感辅助,让你从容应对每一道题。

免费试用即答侠