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

Spring Boot Kotlin DSL配置中Transactional代理引发NPE问题求助

Spring代理Bean在@PostConstruct调用时出现NPE,代理目标实例依赖为空

问题场景

项目禁用了部分Spring Boot自动配置,采用Kotlin DSL手动配置应用。存在带@Transactional方法的抽象登录策略类AbstractUserSignIn,其子类IndividualUserSignIn有Google和Apple两个实现,通过Kotlin DSL注册为Bean,依赖对应的登录服务组件(userSignInService)。

UserSignInFactory通过@Autowired注入这两个IndividualUserSignIn实例集合,出现以下异常情况:

  • 在IndividualUserSignIn自身的@PostConstruct方法(B点)调用getSignInStrategy时,能正常打印支持的策略,此时this为实例本身,所有依赖已正确注入;
  • 在UserSignInFactory的@PostConstruct方法(C点)遍历集合调用getSignInStrategy时,抛出空指针异常(NPE),此时集合中的元素是Spring生成的代理对象,代理指向的实例中userSignInService为空。

原因分析

这是Spring代理创建时机与@PostConstruct执行顺序的冲突导致的:

  1. Spring处理带@Transactional的Bean时,会先创建原始实例,完成依赖注入并执行其@PostConstruct方法;
  2. 随后生成代理对象(CGLIB或JDK动态代理),并将代理对象放入容器中;
  3. UserSignInFactory的@PostConstruct执行时机可能早于代理对象的完全初始化,导致注入的代理尚未正确关联到已初始化的原始实例;
  4. 额外注意:如果getSignInStrategy是final方法,CGLIB无法拦截该方法调用,会直接访问代理对象自身的未注入字段,从而触发NPE。

解决方案

方案1:延迟初始化逻辑到所有单例Bean完成初始化后

实现SmartInitializingSingleton接口,替代@PostConstruct。该接口的afterSingletonsInstantiated方法会在所有单例Bean(包括代理对象)完全初始化后执行:

@Component
class UserSignInFactory : SmartInitializingSingleton {

    @Autowired
    private lateinit var signInStrategies: List<IndividualUserSignIn>

    override fun afterSingletonsInstantiated() {
        signInStrategies.forEach { strategy ->
            println(strategy.getSignInStrategy())
        }
    }
}

方案2:强制指定Bean初始化顺序

在UserSignInFactory上添加@DependsOn注解,明确指定依赖的Google、Apple登录策略Bean名称(需与Kotlin DSL中注册的Bean名称一致),确保这些Bean在UserSignInFactory之前完全初始化:

@Component
@DependsOn(["googleUserSignIn", "appleUserSignIn"])
class UserSignInFactory {

    @Autowired
    private lateinit var signInStrategies: List<IndividualUserSignIn>

    @PostConstruct
    fun init() {
        signInStrategies.forEach { strategy ->
            println(strategy.getSignInStrategy())
        }
    }
}

方案3:确保方法可被代理

检查getSignInStrategy方法是否被final修饰,如果是,移除final修饰符(CGLIB代理需要子类化目标类,final方法无法被拦截,会直接调用代理对象的空字段):

// 移除final修饰,允许CGLIB代理拦截
open fun getSignInStrategy(): SignInStrategy {
    // 业务逻辑实现,依赖userSignInService
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 18:33:11