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

SpringBoot打包为JAR时ServletContextEvent.getServletContext返回空的解决方法

问题分析与解决方案

哥们,你这个问题我之前在项目里也碰到过!核心原因是Spring Boot以JAR包方式启动时,嵌入式Servlet容器(比如Tomcat)的初始化逻辑和IDE中直接启动的场景存在时序差异,再加上你的类同时实现了CommandLineRunner和ServletContextListener,两种生命周期接口的执行时机冲突,导致JAR启动时sce.getServletContext()返回null,而IDE启动时刚好时序凑对了所以没问题。

接下来给你两种靠谱的解决办法,都是我亲测有效的:


方案一:改用ServletContextAware接口获取ServletContext

Spring专门提供了ServletContextAware接口,用来让Bean自动获取ServletContext,这种方式完全贴合Spring的生命周期管理,能完美规避时序问题:

修改后的代码如下:

@Order(10)
@Component("startupRunner")
public class StartupRunner implements CommandLineRunner, ServletContextAware {
    private ServletContext application = null;

    @Resource
    private FilmService filmService;
    @Resource
    private WebSiteInfoService webSiteInfoService;
    @Resource
    private LinkService linkService;
    @Autowired
    private WebSiteService webSiteService;

    @Override
    public void run(String... args) throws Exception {
        this.loadData();
    }

    public void loadData() {
        System.out.println("vincent.applicaiton" + application);
        // 加个空判断更稳妥,避免意外情况
        if (application == null) {
            throw new IllegalStateException("ServletContext还没初始化完成呢!");
        }
        application.setAttribute("newestInfoList", webSiteInfoService.list(null, 0, 10));
        Film film = new Film();
        film.setHot(1);
        application.setAttribute("newestHotFilmList", filmService.list(film, 0, 10));
        application.setAttribute("newestIndexHotFilmList", filmService.list(film, 0, 32));
        application.setAttribute("newestWebSiteList", webSiteService.newestList(0, 10));
        application.setAttribute("newestFilmList", filmService.list(null, 0, 10));
        application.setAttribute("linkList", linkService.listAll());
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.application = servletContext;
    }
}

这个改动很简单:去掉ServletContextListener的实现,换成ServletContextAware,实现setServletContext方法让Spring自动注入ServletContext。不管是IDE启动还是JAR包启动,Spring都会在Bean初始化时把有效的ServletContext传给你。


方案二:监听ServletContextInitializedEvent事件

如果你更喜欢用事件驱动的方式,也可以实现ApplicationListener<ServletContextInitializedEvent>接口,这样只有当ServletContext完全初始化完成后,才会触发数据加载操作,从根源上避免空指针:

@Order(10)
@Component("startupRunner")
public class StartupRunner implements ApplicationListener<ServletContextInitializedEvent> {
    @Resource
    private FilmService filmService;
    @Resource
    private WebSiteInfoService webSiteInfoService;
    @Resource
    private LinkService linkService;
    @Autowired
    private WebSiteService webSiteService;

    @Override
    public void onApplicationEvent(ServletContextInitializedEvent event) {
        ServletContext application = event.getServletContext();
        System.out.println("vincent.applicaiton" + application);
        application.setAttribute("newestInfoList", webSiteInfoService.list(null, 0, 10));
        Film film = new Film();
        film.setHot(1);
        application.setAttribute("newestHotFilmList", filmService.list(film, 0, 10));
        application.setAttribute("newestIndexHotFilmList", filmService.list(film, 0, 32));
        application.setAttribute("newestWebSiteList", webSiteService.newestList(0, 10));
        application.setAttribute("newestFilmList", filmService.list(null, 0, 10));
        application.setAttribute("linkList", linkService.listAll());
    }
}

这种方式的好处是逻辑更清晰:明确绑定到ServletContext的初始化完成事件,不用担心时序问题,而且不需要额外维护成员变量存储ServletContext,直接从事件中获取即可。


再啰嗦两句为什么原代码会失效

在Spring Boot JAR启动模式下,嵌入式Servlet容器的启动是由Spring Boot的SpringApplication统筹的,和传统WAR包部署到外部容器的流程不一样。当你的类同时实现CommandLineRunner和ServletContextListener时,CommandLineRunner.run()可能在ServletContextListener.contextInitialized()之前就执行了,或者contextInitialized()执行时,ServletContext还没被容器完全初始化好,所以返回null。而IDE中直接启动时,容器的初始化时序刚好让两个方法的执行顺序符合你的预期,所以没出问题。

内容的提问来源于stack exchange,提问作者Vincent Chan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:23:47