Spring Boot调用自定义restart方法重启时出现空指针异常如何解决
问题根因
- 核心触发点是Spring Boot启动流程顺序和你预期不符:
SpringApplication.run()方法在返回ConfigurableApplicationContext实例之前,就会先发布ApplicationReadyEvent事件。也就是说你标注了@EventListener(ApplicationReadyEvent.class)的saveMaths方法执行时,main方法里的context = SpringApplication.run(...)赋值操作还没执行,静态变量context此时为null,调用context.getBean自然触发空指针。 - 潜在类名引用问题:你主启动类的类名是
MainApplication,但Service类里调用的是Application.restart(),如果没有做类名别名配置,这里本身就会存在类引用错误,可以先检查下是否贴代码时类名没对齐。 - 事务逻辑冲突:
saveMaths方法加了@Transactional注解,事务提交是在方法执行完成(包括finally块执行完)之后才会触发。你在finally里直接关闭Spring上下文,会导致事务提交时数据源、事务管理器等bean已经被销毁,最终事务回滚,业务数据不会持久化到数据库。
修复方案
- 解决空指针核心问题:调整业务逻辑触发时机,等上下文赋值完成后再执行业务代码,修改main方法如下,同时去掉
saveMaths方法上的@EventListener(ApplicationReadyEvent.class)注解,避免重复触发:
public static void main(String[] args) { context = SpringApplication.run(MainApplication.class, args); // 上下文赋值完成后再获取Service实例执行业务逻辑 context.getBean(你自己的Service类名.class).saveMaths(); }
- 调整事务和重启的执行顺序:把重启逻辑移到事务提交之后再执行,不要直接写在当前事务方法的finally块里,可以用
TransactionSynchronizationManager注册事务提交后的回调:
@Transactional public void saveMaths() { int maths = 0; List<User> users; try { users = userRepository.findByMathsWhereNull(); for (User user : users) { maths = calculateMaths(user); user.setMaths(maths); userRepository.updateUser(maths, user.getId()); } } finally { // 注册事务提交后执行的回调 if (TransactionSynchronizationManager.isActualTransactionActive()) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { MainApplication.restart(); } }); } else { MainApplication.restart(); } } }
- 优化重启逻辑避免并发问题:给重启逻辑加锁,确保旧上下文完全关闭后再启动新上下文,修改
restart方法如下:
@SpringBootApplication public class MainApplication { private static ConfigurableApplicationContext context; private static final Object RESTART_LOCK = new Object(); public static void main(String[] args) { context = SpringApplication.run(MainApplication.class, args); context.getBean(你自己的Service类名.class).saveMaths(); } public static void restart() { ApplicationArguments args = context.getBean(ApplicationArguments.class); synchronized (RESTART_LOCK) { Thread thread = new Thread(() -> { context.close(); context = SpringApplication.run(MainApplication.class, args.getSourceArgs()); }); thread.setDaemon(false); thread.start(); } } }
内容的提问来源于stack exchange,提问作者Greg
相关产品推荐
相关产品推荐

