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执行顺序的冲突导致的:
- Spring处理带
@Transactional的Bean时,会先创建原始实例,完成依赖注入并执行其@PostConstruct方法; - 随后生成代理对象(CGLIB或JDK动态代理),并将代理对象放入容器中;
UserSignInFactory的@PostConstruct执行时机可能早于代理对象的完全初始化,导致注入的代理尚未正确关联到已初始化的原始实例;- 额外注意:如果
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
相关产品推荐
相关产品推荐

