Spring中两类CGLIB代理对象(实现/未实现接口)的差异分析
问题拆解与核心原因分析
这问题挺典型的,我来一步步帮你理清背后的逻辑:
1. 为什么实现接口的类也用了CGLIB代理?
你原本预期实现接口的类会触发JDK动态代理,但实际用了CGLIB,核心原因是你把代理对象强转为了目标类类型(PersonImpl/Person):
- JDK动态代理只能生成实现目标接口的代理类,没法生成目标类的子类,所以JDK代理对象只能强转为接口类型(比如
PersonInterface),不能强转为PersonImpl这种具体类。 - Spring的
ProxyFactory会自动适配这个场景:当你需要代理对象能强转为目标类类型时,会自动切换为CGLIB代理(哪怕目标类实现了接口)。如果把实现接口测试中的强转代码改成val proxy = proxyFactory.proxy as PersonInterface,此时就会用JDK动态代理了。
2. 两类CGLIB代理的核心差异与异常原因
现在重点看两个CGLIB代理的区别,以及未实现接口的代理返回null的本质原因:
实现接口的CGLIB代理(PersonImpl)
- 这个代理类同时继承
PersonImpl并实现PersonInterface。 - 你调用的
introduce()是接口定义的方法,而PersonImpl中重写的introduce()在Kotlin中默认是open的(实现接口的方法默认允许重写,除非显式加final),所以CGLIB可以正常代理这个方法。 - 调用代理的
introduce()时,CGLIB的方法拦截器会把请求委托给你提前初始化好的目标对象p,自然能拿到p中已经设置的age和name值。
未实现接口的CGLIB代理(Person)
- 这个代理类直接继承
Person,但问题出在introduce()方法的修饰符上:- 你的
AbstractPerson中的introduce()方法没有加open修饰,在Kotlin中默认是final方法。 - CGLIB无法代理
final方法,所以代理类不会重写这个方法,调用proxy.introduce()时,会直接执行代理子类自身继承的final方法。 - 代理子类是一个全新的实例,它的
age和name字段是默认的null(并没有复用目标对象p的字段值),因此返回结果就是age: null name: null。
- 你的
快速解决方案
要让未实现接口的代理也能正常返回值,只需要给AbstractPerson中的introduce()方法加上open修饰符即可:
abstract class AbstractPerson(var age: Int? = null, var name: String? = null) { fun init() { this.age = 31 this.name = "LichKing" } open fun introduce(): String = "age: $age name: $name" // 添加open修饰符 }
这样CGLIB就能代理introduce()方法,调用时会委托给目标对象p,拿到正确的字段值。
内容的提问来源于stack exchange,提问作者이창용
相关产品推荐
相关产品推荐

