You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,提问作者이창용

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 07:48:07