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

Spring Boot CGLIB代理Bean构造注入字段调用时为空问题

Spring CGLIB代理导致Repository中DAO字段为空的问题

问题代码

SapArticleRepository实现

@Repository
class SapArticleRepository(jooqDsl: DSLContext, jooqConfiguration: DefaultConfiguration) :
    AbstractSapRepository<TrmSapArticleRecord, TrmSapArticlePojo>(jooqDsl, jooqConfiguration)

基类AbstractSapRepository

abstract class AbstractSapRepository<R : UpdatableRecord<R?>, TPojo>(
    var dao: DAOImpl<R, TPojo, Long>,
    jooqDsl: DSLContext,
    jooqConfiguration: DefaultConfiguration,
) {
    fun findById(id: Long) = dao.findById(id)
}

问题描述

运行集成测试时触发空指针异常,根源是dao字段为空。调试发现:构造函数执行期间dao是正常初始化的,但调用findById方法时dao却变成了null。确认是Spring CGLIB代理导致的——代理对象和原始对象引用不同,但搞不清楚从构造函数执行到代理Bean创建的过程中到底哪里出了问题。

试过在SapArticleRepository里实现抽象方法getDaoImpl()来返回DAO实例,通过方法访问避免空指针,但这种方式太繁琐,想找更简洁的办法,保证构造参数/字段在Bean使用时始终可用。

注:TrmSapArticleDao是jOOQ自动生成的类。
补充:已经查过Stack Overflow相关问题,但已接受的答案解决不了我的问题。


解决方案

1. 把dao字段改为val并添加open修饰符

Spring CGLIB代理会生成目标类的子类做增强,如果字段是var且没被正确代理,很容易出现字段值丢失的情况。把字段改成不可变的val,同时加open允许代理子类访问:

abstract class AbstractSapRepository<R : UpdatableRecord<R?>, TPojo>(
    open val dao: DAOImpl<R, TPojo, Long>,
    jooqDsl: DSLContext,
    jooqConfiguration: DefaultConfiguration,
) {
    fun findById(id: Long) = dao.findById(id)
}

2. 直接传入jOOQ生成的具体DAO实例

既然TrmSapArticleDao是jOOQ自动生成的,直接在子类构造时传入该DAO的实例,避免基类依赖模糊的自动注入:

@Repository
class SapArticleRepository(
    jooqDsl: DSLContext,
    jooqConfiguration: DefaultConfiguration
) : AbstractSapRepository<TrmSapArticleRecord, TrmSapArticlePojo>(
    TrmSapArticleDao(jooqConfiguration),
    jooqDsl,
    jooqConfiguration
)

3. 使用private val确保字段不可变

把dao字段改成private val,这样方法访问的是原始对象的字段,而非代理对象的同名字段,Spring代理会正确传递构造参数到原始对象:

abstract class AbstractSapRepository<R : UpdatableRecord<R?>, TPojo>(
    private val dao: DAOImpl<R, TPojo, Long>,
    jooqDsl: DSLContext,
    jooqConfiguration: DefaultConfiguration,
) {
    fun findById(id: Long) = dao.findById(id)
}

背后的原因

Spring CGLIB代理通过创建目标类的子类实现功能增强,代理对象会调用原始对象的方法,但如果字段是var,代理对象的同名字段不会自动继承原始对象的初始化值——构造函数是给原始对象的字段赋值,代理对象的字段没被初始化,调用方法时用的是代理对象的字段,就会出现空指针。用val或private修饰字段,能确保方法访问的是原始对象的字段,避免代理导致的空值问题。


内容的提问来源于stack exchange,提问作者tiefenauer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 01:01:18