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
相关产品推荐
相关产品推荐

