如何将 OsgiBundleXmlApplicationContext 设置为 WebApplicationContext 的父级

How to set OsgiBundleXmlApplicationContext as parent of WebApplicationContext

在我的 Camel (2.14.0) 应用程序中,我使用 Spring Web 服务来触发 Camel 路由。该工件构建为 OSGi 包并部署在 Karaf (3.0.2) 中。

对于第一个版本,我将 spring-ws 配置为通过 org.springframework.remoting.support.SimpleHttpServerFactoryBean 使用 JVM 内部 Web 服务器来公开 Web 服务。这很好用。但不是很 OSGi-ish。因此,我想将 org.springframework.ws.transport.http.MessageDispatcherServlet 作为对 Karaf whiteboard extender 的服务发布,如下所示:

<bean id="pas-ws-patient-servlet" class="org.springframework.ws.transport.http.MessageDispatcherServlet">
    <property name="contextConfigLocation" value="/endpoint-mapping.xml" /> 
</bean>

<osgi:service ref="pas-ws-patient-servlet" interface="javax.servlet.http.HttpServlet">
    <service-properties>
        <entry key="alias" value="/${pas.ws.patient.contextroot}" />
    </service-properties>
</osgi:service>

这对 "regular" servlet 来说就像一个魅力。但是 MessageDispatcherServlet 想要构建自己的 WebApplicationContext 并希望在该上下文中找到类型为 org.springframework.ws.server.EndpointMapping 的 bean。 Camel 提供了 EndpointMapping 的实现,必须与其 spring-ws 组件一起使用。

我面临的问题是端点映射 bean 的同一个实例必须在创建 Camel 上下文的 OsgiBundleXmlApplicationContextMessageDispatcherServlet 创建的应用程序上下文之间共享。如果我的 OsgiBundleXmlApplicationContextWebApplicationContext 的父级,情况就会如此。尽管如何将 WebApplicationContext 的父上下文设置为 "current" 上下文(我从中发布 servlet 作为服务)让我感到困惑。

OsgiBundleXmlApplicationContext 中实例化一个 WebApplicationContext 以将其传递给 MessageDispatcherServlet 给我一个例外:

java.lang.IllegalArgumentException: Cannot resolve ServletContextResource without ServletContext

不幸的是,MessageDispatcherServletWebServiceMessageReceiver(封装了 EndpointMapping)是私有成员。所以我不能直接设置映射 bean。

有没有办法创建上下文层次结构?或者可以用另一种方式跨上下文共享 bean 实例吗?

解决方案实际上很简单,并记录在 FrameworkServlet 的 JavaDoc 中,MessageDispatcherServlet 扩展了:

可以在 MessageDispatcherServlet 上设置 ApplicationContextInitializer。使用上下文初始值设定项将当前应用程序上下文设置为 servlet 应用程序上下文的父级。为此,您还必须实现 ApplicationContextAware 接口以获取当前上下文(在本例中为 OsgiBundleXmlApplicationContext)。然后将servlet注册为服务:

<!-- This bean sets our current context as the parent context of the XmlWebApplicationContext 
    that the MessageDispatcherServlet insists on creating. -->
<bean id="springWsContextInitializer" class="x.x.x.SpringWsContextInitializer" />

<bean id="spring-ws-servlet" class="org.springframework.ws.transport.http.MessageDispatcherServlet">
    <!-- We inherit all beans from the current context, so no need to specify a separate context file. -->
    <property name="contextConfigLocation" value="" />
    <property name="ContextInitializers" ref="springWsContextInitializer" />
</bean>

<osgi:service ref="spring-ws-servlet" interface="javax.servlet.http.HttpServlet">
    <service-properties>
        <entry key="alias" value="/${servlet.contextroot}" />
        <entry key="servlet-name" value="spring-ws-servlet" />
    </service-properties>
</osgi:service>

上下文初始值设定项class:

public class SpringWsContextInitializer implements ApplicationContextInitializer<XmlWebApplicationContext>, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void initialize(XmlWebApplicationContext applicationContext) {
        applicationContext.setParent(this.applicationContext);
    }
}

为了使用 blueprint 而不是 spring-dw 来实现相同的功能,我必须将 SpringWsContextInitializer 更改为这样:

public class SpringWsContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private final EndpointMapping endpointMapping;

    public SpringWsContextInitializer(final EndpointMapping endpointMapping) {
        this.endpointMapping = endpointMapping;
    }

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        StaticApplicationContext parentContext = new StaticApplicationContext();
        parentContext.refresh();
        parentContext.getDefaultListableBeanFactory().registerSingleton("endpointMapping", this.endpointMapping);
        applicationContext.setParent(parentContext);
    }
}

应该可以将端点映射 bean 发布为 OSGi 服务,然后在 servlet 的上下文文件中引用服务 bean,但是 spring 上需要 OSGi 命名空间的 OSGi 命名空间处理程序无法解析上下文文件。