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

How to use JAXB 2.2.11 in osgi (Liferay DXP)

我正在尝试在 osgi 环境 (Liferay DXP) 中使用 JAXB 2.2.11。我在创建 JAXBContext 时遇到问题。基于在 this and this 等研究过程中发现的一些其他来源,我确定在 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 的第 3 方 jar 中(我刚刚删除了现在通过编写一些测试 JAXB 代码从等式中获取第 3 方 jar。

了解它应该如何完成的最佳地点是 Apache Karaf。它不安装任何 JAXB-API 包 - 而是在 lib/endorsed 目录中使用 org.apache.servicemix.specs.jaxb-api-2.2-2.7.0.jar

这样您就不会使用 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

经过大量研究,我找到了以下解决方案。由于似乎按照 this post 中接受的答案所建议的那样传递捆绑包 class 加载程序必须是正确的,因此我遵循了弄清楚为什么在尝试时会收到 NullPointerException 的路径。在仔细查看 jaxb-api 的源代码以跟踪 NullPointerException 的堆栈跟踪之后,我可以看到 jaxb-api 代码执行类似

的操作

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

其中 classLoader 是我的 bundle 的 class 加载器(因为这是我传入的内容)并且 ContextFactory 实际上是 jaxb-impl 中的 class ,它由bootstrap class 加载程序。这就是问题所在,因为我的 bundle 的 classloader 将无法看到 bootstrap class loader 加载的 classes。这让我很困惑,因为我不习惯 class 加载器在 osgi 中的工作方式。我错误地认为由 bootstrap class 加载程序加载的 classes 是可见的,因为我习惯于在有委托的地方加载 Web 应用程序 class。在 osgi class 加载器中 彼此完全隔离,事物只有在导出时才可见。为了解决这个问题,我发现 some helpful posts 在谈论类似的问题。原来有一个概念 在 osgi 中称为启动委托,您可以在其中指定 classes/packages 列表,以始终通过 bootstrap class 加载程序加载。所以最后的结果就是两步:

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

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) 使用引导委托机制指定要加载的包。此列表需要包含您需要加载的 classes 的传递依赖项。就我而言,我使用的是 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

这里是使用 JDK 11、Liferay DXP/7.2、OSGI 以及从 Dev Studio 创建的示例 Jax-RS Web 服务为我工作的解决方法。我在尝试访问 Web 服务时遇到的错误如下:

JAXBException occurred : Implementation of JAXB-API has not been found on module path or classpath.. com.sun.xml.internal.bind.v2.ContextFactory cannot be found by 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