Spring Boot迁移至Ubuntu后ApplicationContextProvider抛出NullPointerException
问题
Spring Boot 3.2.4应用在Windows 10/Server 2019上运行正常,但迁移至Ubuntu 24.04后,调用ApplicationContextProvider.getApplicationContext()抛出NullPointerException,所有环境均使用openjdk-18.0.1。
相关代码
ApplicationContextProvider(实现ApplicationContextAware)
@Component public class ApplicationContextProvider implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException { ApplicationContextProvider.applicationContext = applicationContext; } public static ApplicationContext getApplicationContext() { return applicationContext; } }
报错触发类MainBuilderUtil
@Component public class MainBuilderUtil { MainChannelCredentialsUtil mainChannelCredentialsUtil; public final OAuth2Credential credentialMain = new OAuth2Credential("provider", ApplicationContextProvider.getApplicationContext().getBean(MainChannelCredentialsUtil.class).getMainToken()); @Autowired private MainBuilderUtil(MainChannelCredentialsUtil mainChannelCredentialsUtil) { this.mainChannelCredentialsUtil = mainChannelCredentialsUtil; } public final Client ClientMain = ClientBuilder.builder() .withEnableChat(true) .withChatAccount(credentialMain) .build(); //getters }
报错堆栈信息
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.bot.springbootbot.connections.channels.builder_utils.MainBuilderUtil]: Constructor threw exception at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:221) ~[spring-beans-6.1.5.jar!/:6.1.5] at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:111) ~[spring-beans-6.1.5.jar!/:6.1.5] at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:315) ~[spring-beans-6.1.5.jar!/:6.1.5] ... 24 common frames omitted Caused by: java.lang.NullPointerException: Cannot invoke "org.springframework.context.ApplicationContext.getBean(java.lang.Class)" because the return value of "com.bot.springbootbot.ApplicationContextProvider.getApplicationContext()" is null at com.bot.springbootbot.connections.channels.builder_utils.MainBuilderUtil.<init>(MainBuilderUtil.java:18) ~[!/:0.0.1-SNAPSHOT] at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62) ~[na:na] at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) ~[na:na] at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) ~[na:na] at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:208) ~[spring-beans-6.1.5.jar!/:6.1.5] ... 26 common frames omitted
启动警告信息
WARN 6417 --- [SpringBootBot] [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mainBuilderUtil' defined in URL [jar:nested:/usr/SpringBootBot/target/SpringBootBot-0.0.1-SNAPSHOT.jar/!BOOT-INF/classes/!/com/bot/springbootbot/connections/channels/builder_utils/MainBuilderUtil.class]: Failed to instantiate [com.bot.springbootbot.connections.channels.builder_utils.MainBuilderUtil]: Constructor threw exception
已知该类无需作为Spring Bean,但此前Windows环境可行,更换JDK无效,需明确跨系统报错原因及解决办法。
原因分析与解决办法
核心原因
Windows与Linux环境下Spring Bean的初始化顺序存在差异。Windows环境中ApplicationContextProvider恰好先于MainBuilderUtil完成初始化,静态变量被赋值;但Ubuntu环境中,MainBuilderUtil的实例化(包含成员变量credentialMain的初始化)早于ApplicationContextProvider的setApplicationContext方法执行,导致静态applicationContext为null,触发空指针异常。
这种依赖初始化顺序的实现本身不具备稳定性,Windows环境能运行只是巧合,并非正确写法。
解决办法
方案1:移除Spring Bean管理,手动初始化(推荐,符合你提到的该类无需作为Bean的需求)
- 删除
MainBuilderUtil上的@Component注解,不再让Spring接管该类的实例化。 - 修改代码,通过构造函数传入依赖,彻底避免依赖静态上下文:
public class MainBuilderUtil { private final MainChannelCredentialsUtil mainChannelCredentialsUtil; public final OAuth2Credential credentialMain; public final Client ClientMain; public MainBuilderUtil(MainChannelCredentialsUtil mainChannelCredentialsUtil) { this.mainChannelCredentialsUtil = mainChannelCredentialsUtil; this.credentialMain = new OAuth2Credential("provider", mainChannelCredentialsUtil.getMainToken()); this.ClientMain = ClientBuilder.builder() .withEnableChat(true) .withChatAccount(credentialMain) .build(); } // getters }
- 在需要使用
MainBuilderUtil的Spring Bean中,通过注入的MainChannelCredentialsUtil手动创建实例:
@Autowired private MainChannelCredentialsUtil credentialsUtil; public void businessMethod() { MainBuilderUtil builderUtil = new MainBuilderUtil(credentialsUtil); // 使用builderUtil完成业务逻辑 }
方案2:保留Spring Bean,修正初始化时机
如果必须让Spring管理MainBuilderUtil,则需要避免在成员变量初始化阶段依赖静态上下文,改用@PostConstruct注解在Bean完全初始化后执行逻辑:
@Component public class MainBuilderUtil { @Autowired private MainChannelCredentialsUtil mainChannelCredentialsUtil; public OAuth2Credential credentialMain; public Client ClientMain; @PostConstruct public void init() { this.credentialMain = new OAuth2Credential("provider", mainChannelCredentialsUtil.getMainToken()); this.ClientMain = ClientBuilder.builder() .withEnableChat(true) .withChatAccount(credentialMain) .build(); } // getters }
此方式下,credentialMain和ClientMain会在Spring完成mainChannelCredentialsUtil注入,且整个Bean初始化完成后再初始化,彻底摆脱对不稳定初始化顺序的依赖。
内容的提问来源于stack exchange,提问作者maximuz666

