Pax Web 8迁移Jetty 9时Servlet上下文重启资源管理异常排查
问题:Pax Web 8迁移中Bundle重启时Servlet注销异常
场景与配置
基于Pax Web 8和Jetty 9的应用,正从Pax Web 7迁移至8,已通过OSGI HTTP白板配置Servlet、资源映射,并自定义了ServletContextHelper服务工厂以确保Bundle能访问自身资源。资源可正常访问,但在注册Servlet的Bundle停止/重启时,出现资源管理异常。
Servlet的Blueprint配置
<service> <service-properties> <entry key="osgi.http.whiteboard.servlet.pattern" value="/logout/*"/> <entry key="osgi.http.whiteboard.servlet.name" value="localLogout"/> <entry key="osgi.http.whiteboard.context.select" value="(osgi.http.whiteboard.context.name=customContext)"/> <entry key="org.eclipse.jetty.servlet.SessionIdPathParameterName" value="none"/> <entry key="org.eclipse.jetty.servlet.SessionPath" value="/"/> <entry key="osgi.http.whiteboard.servlet.asyncSupported" value="true"/> </service-properties> </service>
资源映射的Blueprint配置
<service interface="org.ops4j.pax.web.service.whiteboard.ResourceMapping"> <service-properties> <entry key="osgi.http.whiteboard.servlet.name" value="${pkg.name}-resource-mapping"/> <entry key="osgi.http.whiteboard.resource.pattern" value="${pkg['context-path']}/*"/> <entry key="osgi.http.whiteboard.context.select" value="(osgi.http.whiteboard.context.name=customContext)"/> </service-properties> </service>
自定义ServletContextHelper服务工厂代码
public class Activator implements BundleActivator { private static final Logger LOGGER = LoggerFactory.getLogger(Activator.class); private ServiceRegistration<ServletContextHelper> registration; @Override public void start(BundleContext bundleContext) throws Exception { Dictionary<String, Object> properties = new Hashtable<>(); properties.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME, "customContext"); properties.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH, "/"); // properties.put(HttpWhiteboardConstants.HTTP_SERVICE_CONTEXT_PROPERTY, ""); properties.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE); registration = bundleContext.registerService( ServletContextHelper.class, new DefaultServletContextHelperServiceFactory(), properties); } @Override public void stop(BundleContext bundleContext) throws Exception { if (registration != null) { registration.unregister(); registration = null; } } private static class DefaultServletContextHelperServiceFactory implements ServiceFactory<ServletContextHelper> { @Override public ServletContextHelper getService( Bundle bundle, ServiceRegistration<ServletContextHelper> registration) { return new DefaultServletContextHelper(bundle); } @Override public void ungetService( Bundle bundle, ServiceRegistration<ServletContextHelper> registration, ServletContextHelper service) {} } }
异常栈追踪
rnal.framework.BundleContextImpl 968 | 396 - org.ops4j.pax.web.pax-web-extender-whiteboard - 8.0.24 | FrameworkEvent ERROR java.lang.RuntimeException: Servlet with reference "{javax.servlet.Servlet}={osgi.http.whiteboard.servlet.name=servletName, service.id=1551, osgi.service.blueprint.compname=servletName, service.bundleid=860, service.scope=bundle, osgi.http.whiteboard.servlet.pattern=/servletroute/*, osgi.http.whiteboard.context.select=(osgi.http.whiteboard.context.name=customContext)}" was never registered by bundle-name [860] at org.ops4j.pax.web.service.internal.HttpServiceEnabled.lambda$doUnregisterServlet$2(HttpServiceEnabled.java:707) ~[?:?] at org.ops4j.pax.web.service.spi.model.ServerModel.lambda$run$2(ServerModel.java:541) ~[?:?] at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768) ~[?:?] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) ~[?:?] at java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[?:?] at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) ~[?:?] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[?:?] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[?:?] at java.lang.Thread.run(Thread.java:840) ~[?:?] Suppressed: java.lang.Throwable
问题分析
这个异常是Pax Web在尝试注销一个未被正确关联的Servlet实例,核心原因是ServletContextHelper与Servlet/资源映射的生命周期不匹配:
- 自定义ServiceFactory仅实现了ServletContextHelper的创建逻辑,但未在Bundle释放上下文时清理关联资源,导致Pax Web内部注册记录残留;
- ServletContextHelper的注销时机早于Servlet的注销,当Pax Web扩展器后续处理Servlet注销时,找不到对应的上下文关联记录;
- Blueprint注册的Servlet为bundle作用域,Bundle停止时Blueprint会先注销Servlet服务,但Pax Web的异步处理逻辑可能还在尝试基于上下文做注销操作,出现记录不一致。
解决方案
1. 配置上下文持久化,延迟上下文销毁
在Activator的start方法中,给ServletContextHelper添加持久化属性,让上下文在关联Bundle停止后保留,直到显式注销,避免提前销毁导致的记录异常:
@Override public void start(BundleContext bundleContext) throws Exception { Dictionary<String, Object> properties = new Hashtable<>(); properties.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME, "customContext"); properties.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH, "/"); properties.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE); // 添加上下文持久化属性 properties.put("org.osgi.service.http.whiteboard.context.persistence", true); registration = bundleContext.registerService( ServletContextHelper.class, new DefaultServletContextHelperServiceFactory(), properties); }
2. 完善ServiceFactory的ungetService方法,清理上下文资源
修改DefaultServletContextHelperServiceFactory的ungetService方法,显式清理上下文资源,确保Pax Web内部记录同步:
@Override public void ungetService( Bundle bundle, ServiceRegistration<ServletContextHelper> registration, ServletContextHelper service) { // 调用DefaultServletContextHelper的destroy方法清理资源 if (service instanceof DefaultServletContextHelper) { ((DefaultServletContextHelper) service).destroy(); } }
3. 调整Blueprint中Servlet的依赖顺序
在Blueprint的Servlet配置中添加depends-on属性,依赖于自定义的ServletContextHelper服务,确保Servlet在上下文之后启动、在上下文之前停止:
<service ... depends-on="customContextHelper"> <service-properties> <!-- 原Servlet属性配置 --> </service-properties> </service>
同时在Activator的start方法中给ServletContextHelper添加服务ID,方便Blueprint引用:
properties.put(Constants.SERVICE_ID, "customContextHelper");
4. 升级Pax Web版本
当前使用的Pax Web 8.0.24存在白板扩展器注销逻辑的已知bug,升级到8.x最新稳定版(如8.0.30及以上),官方已修复部分生命周期不一致的问题。
内容的提问来源于stack exchange,提问作者Steve
相关产品推荐
相关产品推荐

