OSGI CXF单例类加载问题

时间:2012-02-14 18:16:06

标签: singleton osgi classloader apache-servicemix fuseesb

这个问题现在是Bounty!解决这个问题的第一个答案获胜。

所以我最近发现OSGI中的bundle并不是100%彼此隔离,特别是当你的bundle共享一个包含单例的公共bundle时,这会导致两个不相关的bundle覆盖单例。这个问题已经体现在CXF库中。让我举一个详细的例子说明正在发生的事情:

我们在FuseESB ServiceMix(一个osgi平台)中捆绑了A,B和共享捆绑包CXF。 CXF的Bus类是一个单例,由于OSGI每个bundle有一个类加载器,它将与使用CXF的每个其他bundle共享这个单例。所以我似乎无法为bundle A和bundle B创建不同的总线,这一点很重要,因为bundle A应该使用SSL而bundle B不应该使用SSL。考虑到捆绑包A和捆绑包B彼此之间没有任何关系,除了它们必须在同一个ServiceMix上一起部署之外,这更令人沮丧。

现在我已经解决了这个问题一段时间(1-2个月),我已经阅读了很多不同的解决方案。然而问题是很多解决方案要求我完全控制源代码,在这种情况下我不需要。我正在创建的捆绑包是使用一些名为Xenara的专有第三方非osgi库,它使用CXF。由于我无法控制的商业原因,我必须使用这个第三方库。幸运的是,我可以访问此库使用的CXF spring bean文件。

我对解决这个问题的猜测是我需要一些如何使它成为捆绑A可以使用自己的个人CXF实例,或者至少使它实例化其不与其他捆绑包共享的CXF总线。以下是我尝试或考虑的方法:

  1. 我将CXF嵌入到捆绑包A中,但不幸的是,类加载器不断从捆绑包A外部获取CXF,而不是在类路径上查找。在束A之外搜索之前,从来没有想过如何强制它在束A中搜索CXF。

  2. 建议将捆绑包A制作成服务。我认为存在一些误解,人们认为单身人士在A而不在CXF。无论我尝试过它并没有解决问题。仍然在捆绑A和B之间共享CXF总线。

  3. 重写类加载,以便bundle A使用不同的类加载器来加载CXF类。我不完全理解这个的逻辑,但我确信这将是非常棘手的,因为使用spring bean来创建CXF总线和http-conduit。请参阅下面的(4)以获得更好的主意。

  4. 在CXF中,有一种方法可以为给定的线程上下文设置CXF总线和http-conduit。我真的想使用这个解决方案,但我无法弄清楚如何将CXF bean文件转换为等效的java代码。下面提供了CXF spring bean文件。注意我无法使用此http-conduit访问源代码,这就是为什么我没有使用this link here中的“使用Java代码”中显示的示例,因为我无法访问SOAPService, wsdl等...

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
        <property name="searchSystemEnvironment" value="true" />
        <property name="ignoreUnresolvablePlaceholders" value="true" />
    </bean>
    
    <cxf:bus>
        <cxf:outInterceptors>           
            <bean class="com.xenara.messaging.security.IdentityAssertingOutInterceptor"
                  scope="singleton" />
        </cxf:outInterceptors>
    
        <cxf:features>
            <wsa:addressing xmlns:wsa="http://cxf.apache.org/ws/addressing"/>
        </cxf:features>
    </cxf:bus>
    
    <http-conf:conduit name="*.http-conduit">
        <http-conf:client AllowChunking="false" Connection="Keep-Alive" />
        <http-conf:tlsClientParameters disableCNCheck="true" secureSocketProtocol="TLS">
            <sec:keyManagers keyPassword="${javax.net.ssl.keyStorePassword}">
                <sec:keyStore type="JKS" password="${javax.net.ssl.keyStorePassword}"
                                         file="${javax.net.ssl.keyStore}" />
            </sec:keyManagers>
            <sec:trustManagers>
                <sec:keyStore type="JKS" password="${javax.net.ssl.trustStorePassword}" file="${javax.net.ssl.trustStore}" />
            </sec:trustManagers>
            <sec:cipherSuitesFilter>
                <sec:include>SSL_RSA_WITH_3DES_EDE_CBC_SHA</sec:include>
                ...
            </sec:cipherSuitesFilter>
        </http-conf:tlsClientParameters>
    </http-conf:conduit>
    

2 个答案:

答案 0 :(得分:2)

这听起来像OSGi对我的基本原则:提供隔离,但你可以在常规OSGi中做很多事情;例如,修改类的静态成员,并且由于你们都共享该类(A可能是导出它,B和C导入它),其他人会注意到。

在大多数情况下,我建议你不要使用静态类状态,因为它必然会混淆其他包的内容。

在您的情况下,在我看来,捆绑包A是一个在框架中没有真正使用的库。如果你需要真正的隔离,我会将库打包在使用bundle的两个中,而不用担心过多的开销。

记录:这种情况与Servicemix无关,它是基本的Java:如果我们谈论的是同一个类,有人更改了静态属性,其他人会注意到。如果这种情况让您感到困惑,您可以阅读一下OSGi中的类加载和共享机制。

答案 1 :(得分:2)

您面临的问题是相当重要和基本的。您在支持库CXF中具有静态状态,而您仍然希望使用CXF共享库的实例。您无法修改共享库(由于其庞大的规模),也无法修改CXF(闭源?)。我们将这些共享库称为Foo和Bar。

假设您有以下类:

CXF#1
Foo#1, using CXF#1
Bar#1, using CXF#1
WebApp#1, using Foo#1 and Bar#1

如果我理解正确,您现在希望另一个应用程序使用相同的Foo和Bar实例,而不使用相同的底层库CXF#1。这相当于以下情况。

CXF#2
CXF#1
Foo#1, using CXF#1 when called by App#1, using CXF#2 when called by App#2
Bar#1, using CXF#1 when called by App#1, using CXF#2 when called by App#2
WebApp#1, using Foo#1 and Bar#1
WebApp#2, using Foo#1 and Bar#1

这是不可能的;不在OSGi中,也不在任何Java框架中。 现有的类无法动态绑定到另一个类,根据调用Bundle进行选择。在不修改库的情况下执行此操作的唯一方法是复制支持库:

CXF#2
CXF#1
Foo#1, using CXF#1
Bar#1, using CXF#1
Foo#2, using CXF#2
Bar#2, using CXF#2
WebApp#1, using Foo#1 and Bar#1
WebApp#2, using Foo#2 and Bar#2

实际上,这需要付出很多努力,并会爆炸磁盘和内存中的软件包数量。如果CXF软件包只能由单个应用程序使用,那么最合理的解决方案是复制软件包并将其嵌入到您使用它的任何位置。是的,这包括包依赖的任何和所有库。

解决此问题的一种黑客/冒险方法如下。您应该能够反编译CXF类。这将允许您按如下方式修改类:

class CXF {
    [...]
    public static CXF getInstance() {
        // based on the current Stack frame, determine which instance to return. Remember, the instance should be based on the WebApp bundle (while you still have shared libraries in between!)
    }
}

这不是万无一失的。假设您的WebApp启动一个源自库A的回调线程。该线程调用CXF.getInstance() - &gt; getInstance()方法无法确定哪个WebApp启动了回调线程。

正确的解决方案是修改所有库以不使用Singleton模式。你可以通过实现一个特殊的类加载器来解决问题,但是这会打开另外一堆蠕虫。

- 编辑 - 在阅读了CXF之后,CXF公开Singleton类似乎很奇怪。这件事是为OSGi做的!你可能最好在CXF邮件列表上提问;他们会知道制作单例实例的所有特殊糖和原因,并且可能已经考虑过这个用例。