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

Spring Web应用中ServletContextListener使用的异常问题

ServletContext存储对象的一致性问题及JxBrowser Engine失效排查

ServletContext本身是Web应用的全局上下文,存储的对象不会随时间自动出现不一致。但你的代码实现存在几个关键问题,导致运行一段时间后Engine对象失效,进而抛出InvocationTargetException。

问题分析

1. 静态变量与ServletContext的冗余存储冲突

你在SampleContextListener中同时维护了静态的engine和context变量,又将engine存入ServletContext。静态变量的生命周期绑定类加载器,而ServletContext的生命周期是Web应用的启停周期:

  • 当应用销毁后,若类加载器未被回收,静态变量可能残留无效的Engine引用;
  • TestClass直接通过静态SampleContextListener.context获取上下文,一旦Context销毁后这个静态变量未被正确清理,会导致空指针或访问已失效的Context。

2. JxBrowser Engine的状态失效

JxBrowser的Engine是基于Chromium内核的重量级对象,长时间运行可能因以下原因失效:

  • Chromium子进程崩溃或内存泄漏
  • 系统资源耗尽(如文件句柄、内存)
  • Engine长时间闲置后被系统隐性回收
    此时调用engine.newBrowser()会抛出底层异常,被包装为InvocationTargetException。

3. 销毁逻辑顺序错误

在contextDestroyed方法中,你先将context置为null,再关闭Engine:

  • 销毁过程中若有线程仍通过静态context访问属性,会直接抛出空指针;
  • ServletContext中的engine属性未被移除,可能残留无效引用。

解决方案

1. 移除冗余静态变量,统一从ServletContext获取Engine

删掉SampleContextListener中的静态engine和context变量,所有操作都通过ServletContext实现,避免生命周期不一致:

@WebListener
public class SampleContextListener implements ServletContextListener {
    public static final Logger LOGGER = LogManager.getRootLogger();
    public static String browserDirectory = "/tmp";

    @Override
    public void contextInitialized(ServletContextEvent event) {
        Engine engine = Engine.newInstance(EngineOptions.newBuilder(HARDWARE_ACCELERATED)
                .licenseKey("")
                .chromiumDir(Paths.get(browserDirectory))
                .build());
        LOGGER.info("Engine Initialized => " + engine);
        ServletContext context = event.getServletContext();
        context.setAttribute("engine", engine);
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        ServletContext context = event.getServletContext();
        Engine engine = (Engine) context.getAttribute("engine");
        try {
            if (engine != null && !engine.isClosed()) {
                engine.close();
                LOGGER.info("Engine Closed => " + engine);
            }
        } catch (IOException e) {
            LOGGER.info("Error While Deleting Browser Files: " + e.getMessage());
        } finally {
            context.removeAttribute("engine");
        }
    }
}

2. 增加Engine健康检查机制

在使用Engine前先校验其有效性,若失效则重新初始化(需注意线程安全,避免并发初始化):

public class TestClass {
    public static final Logger LOGGER = LogManager.getRootLogger();

    public static Browser getBrowser(ServletContext servletContext) {
        Engine engine = (Engine) servletContext.getAttribute("engine");
        // 检查Engine是否有效
        if (engine == null || engine.isClosed()) {
            // 加锁避免并发初始化
            synchronized (TestClass.class) {
                engine = (Engine) servletContext.getAttribute("engine");
                if (engine == null || engine.isClosed()) {
                    engine = Engine.newInstance(EngineOptions.newBuilder(HARDWARE_ACCELERATED)
                            .licenseKey("")
                            .chromiumDir(Paths.get("/tmp"))
                            .build());
                    servletContext.setAttribute("engine", engine);
                    LOGGER.info("Reinitialized Engine => " + engine);
                }
            }
        }
        return engine.newBrowser();
    }
}

3. 改用Spring Bean管理Engine生命周期

作为Spring Web应用,推荐用Spring的Bean生命周期管理替代ServletContextListener,更符合生态规范:

@Configuration
public class JxBrowserConfig {
    @Bean(destroyMethod = "close")
    public Engine jxBrowserEngine() {
        return Engine.newInstance(EngineOptions.newBuilder(HARDWARE_ACCELERATED)
                .licenseKey("")
                .chromiumDir(Paths.get("/tmp"))
                .build());
    }
}

// 在业务类中注入Engine使用
@Service
public class BrowserService {
    private final Engine engine;

    public BrowserService(Engine engine) {
        this.engine = engine;
    }

    public Browser createBrowser() {
        return engine.newBrowser();
    }
}

4. 排查InvocationTargetException根源

捕获InvocationTargetException时,打印其内部异常(e.getCause()),可以明确是Engine本身的问题(如Chromium进程崩溃)还是系统资源不足,针对性优化(如增加资源监控、调整Chromium启动参数)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 10:15:36