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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 15:15:14