为何@Autowired注入的ApplicationContext为null?Spring Boot 2.7.6问题
问题背景
使用Spring Boot 2.7.6,启动类中通过@Autowired注入的ApplicationContext在getISEARequestHandler()方法中始终为null,该方法创建的ISEARequestHandler被Rest控制器自动注入。同时项目中存在自定义的SEABeanDefinitionRegistryPostProcessor,通过DeferredImportSelector导入。
启动类核心代码:
@SpringBootApplication @MapperScan(basePackages = "fwx.apps.ebidding.**.mappers", annotationClass = fwx.sea.impl.mybatis.annotations.SEAMapper.class) @Import(SEAPostProcessorSelector.class) public class EbiddingApplication { @Autowired private ApplicationContext springContext; @Bean public ISEARequestHandler getISEARequestHandler() { return new MybatisSEARequestHandler(springContext.getBean(MybatisSEAContext.class)); } // 其他@Bean方法... }
原因分析
启动类字段注入时机滞后
Spring初始化时会先实例化启动类,再执行其中的@Bean方法,而@Autowired字段的注入是在启动类实例化之后的依赖注入阶段完成的。这就导致执行getISEARequestHandler()等@Bean方法时,springContext还未被赋值,因此为null。PostProcessor提前触发bean实例化
SEABeanDefinitionRegistryPostProcessor实现了BeanDefinitionRegistryPostProcessor,其postProcessBeanFactory()方法在bean定义注册完成后、bean实例化前执行。方法中调用beanFactory.getBean(ISEAContext.class)会提前触发ISEAContext的实例化,此时启动类的springContext还未完成注入,进而导致ISEAContext及其依赖的ISEARequestHandler都拿到null的ApplicationContext。
解决方案
方案1:使用参数注入替代字段注入(推荐)
Spring支持在@Bean方法中直接声明依赖参数,框架会自动注入对应的bean,无需手动通过ApplicationContext.getBean()获取。这种方式更符合Spring最佳实践,也避免了字段注入的时机问题。
修改启动类中的@Bean方法:
@SpringBootApplication @MapperScan(basePackages = "fwx.apps.ebidding.**.mappers", annotationClass = fwx.sea.impl.mybatis.annotations.SEAMapper.class) @Import(SEAPostProcessorSelector.class) public class EbiddingApplication { // 移除@Autowired的springContext字段 @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); return factoryBean.getObject(); } @Bean public SqlSessionTemplate sqlSession(SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } @Bean public MybatisSEAContext getSEAContext(ApplicationContext springContext) { return new SpringMybatisSEAContext(springContext); } @Bean public ISEARequestHandler getISEARequestHandler(MybatisSEAContext seaContext) { return new MybatisSEARequestHandler(seaContext); } // 其他@Bean方法... }
方案2:实现ApplicationContextAware接口
让启动类实现ApplicationContextAware接口,通过重写setApplicationContext()方法获取容器实例,该方法会在ApplicationContext初始化完成后被调用,确保springContext已赋值。
@SpringBootApplication @MapperScan(basePackages = "fwx.apps.ebidding.**.mappers", annotationClass = fwx.sea.impl.mybatis.annotations.SEAMapper.class) @Import(SEAPostProcessorSelector.class) public class EbiddingApplication implements ApplicationContextAware { private ApplicationContext springContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.springContext = applicationContext; } // 原@Bean方法可保留,此时springContext已被正确赋值 }
调整PostProcessor逻辑,避免提前实例化bean
SEABeanDefinitionRegistryPostProcessor中调用beanFactory.getBean()会提前触发实例化,建议修改逻辑:
- 对于
SEAListener的注册,可改用BeanPostProcessor在bean实例化完成后处理; - 对于Mapper的映射注册,仅在PostProcessor中保存映射元数据,等
ISEAContext初始化完成后再批量设置,避免提前获取bean实例。
示例:改用BeanPostProcessor处理监听器注册
@Component public class SEABeanPostProcessor implements BeanPostProcessor { private Logger log = LoggerFactory.getLogger(getClass()); @Autowired private ISEAContext seaContext; @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { SEAListener listenerAnnotation = bean.getClass().getAnnotation(SEAListener.class); if (listenerAnnotation != null) { String locator = listenerAnnotation.locator(); log.info("注册SEA监听器:[" + bean.getClass().getName() + "] ==> " + locator); seaContext.addListener(locator, bean.getClass().getName()); } return bean; } }
总结
核心问题是启动类字段注入时机晚于@Bean方法执行,以及自定义PostProcessor提前触发bean实例化导致依赖链异常。通过参数注入或ApplicationContextAware可解决ApplicationContext为null的问题,同时调整PostProcessor逻辑能避免提前实例化带来的连锁问题。
内容的提问来源于stack exchange,提问作者WestFarmer

