如何在osgi中使用JAXB 2.2.11(Liferay DXP)

时间:2017-10-03 22:03:11

标签: jaxb osgi osgi-bundle liferay-7

我正在尝试在osgi环境(Liferay DXP)中使用JAXB 2.2.11。我在创建JAXBContext时遇到问题。基于在研究像thisthis时发现的其他一些来源,我已经确定在osgi容器中我需要为JAXB提供正确的类加载器来实例化上下文。所以我有这样的代码:

ClassLoader cl package.with.jaxb.objects.ObjectFactory.class.getClassLoader(); JAXBContext jc = JAXBContext.newInstance("package.with.jaxb.objects ", cl);

此代码导致带有以下堆栈跟踪的空指针异常:

Caused by: java.lang.NullPointerException
    at javax.xml.bind.ContextFinder.handleClassCastException(ContextFinder.java:129)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:201)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:146)
    at javax.xml.bind.ContextFinder.find(ContextFinder.java:371)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:446)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:409)

查看ContextFinder的来源我可以看到第{129行} context必须为空:

throw handleClassCastException(context.getClass(), JAXBContext.class);

我想也许问题是我的模块依赖于jaxb-api 2.2.11但是jaxb-impl类在运行时由rt.jar提供,并且可能比2.2.11更新,因为Liferay DXP在JDK上运行1.8。为了解决这个问题,我尝试将jaxb-impl.jar 2.2.11作为我的osgi模块中的依赖项,然后考虑jaxb-api& jaxb-impl版本会匹配。之后,尝试使用与上面相同的代码创建JAXBContent会导致以下错误:

ClassCastException: attempting to cast jar:file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/rt.jar!/javax/xml/bind/JAXBContext.class to bundleresource://623.fwk616113009:13/javax/xml/bind/JAXBContext.class. Please make sure that you are specifying the proper ClassLoader.

通过此消息的外观,实例化的JAXBContext来自通过rt.jar加载的JAXBContext版本。这对我来说非常混乱,因为我希望使用我的模块的类加载器加载的JAXBContext版本,因为我已经在我的模块中包含了jaxb-impl.jar而且我已经指定了我的模块的类加载器是要用于的模块我对JAXBContext.newInstance的调用。任何人都可以了解如何让jaxb 2.2.11在osgi容器中工作吗?

*请注意我无法升级我的模块使用的jaxb-api版本,因为JAXB代码实际上是在需要jaxb 2.2.11的第三方jar中(我刚刚从第3方jar中删除了现在通过编写一些测试JAXB代码的方程式。)

3 个答案:

答案 0 :(得分:5)

经过广泛研究后,我发现了以下解决方案。因为看起来像this post中接受的答案所建议的那样传递bundle类加载器必须是正确的,所以我沿着找出为什么在我尝试时得到NullPointerException的路径。在仔细查看jaxb-api的源代码以遵循NullPointerException的堆栈跟踪之后,我可以看到jaxb-api代码执行类似

的操作

classLoader.loadClass("com.sun.xml.internal.bind.v2.ContextFactory")

其中classLoader是我的bundle的类加载器(因为那是我传入的),而ContextFactory实际上是jaxb-impl中的一个类,它由bootstrap类加载器加载。这就是问题所在,因为我的 bundle的类加载器无法看到bootstrap类加载器加载的类。这让我感动了一段时间,因为我不习惯类加载器如何在osgi中工作。我错误地认为引导类加载器加载的类是可见的,因为我已经习惯了Web应用程序类加载,其中有委托。在osgi类加载器中 彼此完全隔离,只有在导出时才能看到东西。为了解决这个问题,我发现some helpful posts谈论了类似的问题。事实证明有一个概念 在osgi中调用引导委派,您可以在其中指定始终通过引导类加载器加载的类/包列表。所以最终的结果是两个步骤:

1)在调用代码获取JAXBContext之前,将线程的类加载器切换到bundle类加载器:

ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();

try {           
    // ObjectFactory here is in the same package as my classes to be marshalled
    ClassLoader objectFactoryClassLoader = ObjectFactory.class.getClassLoader();            
    Thread.currentThread().setContextClassLoader(objectFactoryClassLoader);
    // JAXB code goes here
} finally {
    Thread.currentThread().setContextClassLoader(currentClassLoader);
}

2)使用引导委派机制指定要加载的包。此列表需要包含您需要加载的类的传递依赖项。就我而言,我正在使用Liferay列表 特定于Liferay,它位于portal-ext.properties配置文件中。幸运的是,我找到了this post,其中有人为我完成了大部分工作:

module.framework.properties.org.osgi.framework.bootdelegation=\
  __redirected,\
  com.liferay.aspectj,\
  com.liferay.aspectj.*,\
  com.liferay.portal.servlet.delegate,\
  com.liferay.portal.servlet.delegate*,\
  com.sun.ccpp,\
  com.sun.ccpp.*,\
  com.sun.crypto.*,\
  com.sun.image.*,\
  com.sun.jmx.*,\
  com.sun.jna,\
  com.sun.jndi.*,\
  com.sun.mail.*,\
  com.sun.management.*,\
  com.sun.media.*,\
  com.sun.msv.*,\
  com.sun.org.*,\
  com.sun.syndication,\
  com.sun.tools.*,\
  com.sun.xml.*,\
  com.yourkit.*,\
  org.eclipse.persistence.internal.jaxb,\
  org.eclipse.persistence.internal.jaxb.*,\
  javax.xml.*,\
  sun.*

有用的链接:

Why can't JAXB find my jaxb.index when running inside Apache Felix?

What is the difference between bootdelegation and DynamicImport-Package in osgi

https://web.liferay.com/web/user.26526/blog/-/blogs/liferay-dxp-and-weblogic-

https://dev.liferay.com/develop/tutorials/-/knowledge_base/7-0/bundle-classloading-flow

http://apache-felix.18485.x6.nabble.com/Classloading-for-JAXB-td4834670.html

答案 1 :(得分:1)

这是使用JDK 11,Liferay DXP / 7.2,OSGI和从Dev Studio创建的示例Jax-RS Web服务为我解决的变通方法。尝试访问Web服务时出现以下错误:

  

发生JAXBException:找不到JAXB-API的实现   在模块路径或类路径上。   无法找到com.sun.xml.internal.bind.v2.ContextFactory   org.apache.aries.jax.rs.whiteboard_1.0.4。

对我有用的是在系统级别定义上下文工厂以覆盖预定义的上下文工厂。将以下系统变量添加到您的系统中

javax.xml.bind.JAXBContextFactory=com.sun.xml.bind.v2.ContextFactory

例如,您可以将其添加到Tomcat中的setenv.sh/bat文件中,或者在eclipse中,可以在VM参数下访问服务器的“启动配置”的“参数”选项卡

-Djavax.xml.bind.JAXBContextFactory=com.sun.xml.bind.v2.ContextFactory

这项工作无需添加任何额外的库,因为Liferay已经包含了这些库。

这是如何工作的?请参考javadoc for JaxBContext并阅读“发现JAXB实现”部分。使用/META-INF/services/javax.xml.bind.JAXBContext文件对我不起作用。

我希望这对某人有帮助。 对于DXP用户的最后一点说明,如果您的服务遭到拒绝,则您需要阅读有关Service Access Policies

的信息

答案 2 :(得分:0)

看看应该如何做的最好的地方是Apache Karaf。它不会安装任何JAXB-API包 - 而是在org.apache.servicemix.specs.jaxb-api-2.2-2.7.0.jar目录中使用lib/endorsed

这样您就不会使用rt.jar提供的JAXB-API。

实现 - 最好使用ServiceMix版本的JAXB包:

  • org.apache.servicemix.bundles:org.apache.servicemix.bundles.jaxb-IMPL:2.2.11_1
  • org.apache.servicemix.bundles:org.apache.servicemix.bundles.jaxb-XJC:2.2.11_1