Eclipse/OSGI、Java 11、JAXB 和类加载器

Eclipse/OSGI, Java 11, JAXB, and the Classloader

我有两个 Java 模块,A 和 B。A 提供了一个带有 JAXB 注释和帮助程序的核心模型 classes 用于创建执行 JAXB 的东西(创建上下文、编组、解组等) .) B 提供了额外的 classes,它们通过 @XmlAnyElement(lax = true) 包含在模型中,因此必须添加到 JAXB 上下文中。

这在普通 Java 中工作正常 - B 的 classloader 看到所有相关的 classes 并且可以实例化 JAXB 上下文:

JAXBContext.newInstance(RootFromA.class, RootFromB.class)

现在我正在尝试对 OSGI 进行同样的操作(B 是一个 Eclipse 插件,A 是核心库,普通 Java 命令行模块 C 也会使用它)。经过反复试验,我设法让 A 和 B 通过 OSGI 包导入查看 JAXB API 和实现。问题是,如上调用 newInstance 似乎使用了 JAXB API 的 classloader,而不是 RootFromA 的加载器,当然也不是 RootFromB 的加载器。因此,它甚至看不到 JAXB 实现并抱怨找不到 ContextFactory class.

我已经通过调用不同版本的 newInstance 设法解决了这个问题:

JAXBContext.newInstance(
  RootFromA.class.getPackageName()
    + ":" + RootFromB.class.getPackageName(),
  RootFromB.class.getClassLoader())

我不喜欢这个,原因有二:

  1. 我的“客户端”代码(B 是 A 中 JAXB 助手的客户端)必须手动提供合适的 classloader。
  2. 我必须在列出上下文 class 的所有引用包中提供 jaxb.index 文件,即使我的代码完全了解它们并且实际上从 [=44] 中获取包名称=]es.

可能没有任何办法绕过 (1),因为只有 B 知道完整的 classes 集并且可以决定谁的 classloader 能够看到所有这些。我担心一旦我添加扩展模块 C 和 D 通过 Eclipse 扩展点连接到 B 并为 JAXB 上下文提供额外的 classes - B 的 classloader能看到那些?

但我真的会找到一种方法来摆脱 (2) 所需的静态索引文件。完整的上下文集 classes 是动态的,并且在 Java 中由 ServiceLoader 决定,在 Eclipse 中由扩展点决定。在这两种情况下,我都可以直接访问应该属于上下文的完整 classes 集,因此考虑必须手动将 jaxb.index 文件添加到每个冗余包中,因此可能是不必要的来源错误。

我错过了什么吗?有没有“更好”的方法来做到这一点?为什么没有采用一组上下文 classes 和 classloader 的 newInstance 方法?

看来我找到了解决办法。我的问题是为了两个不同的目的不得不处理类加载器:

  1. JAXB 用来实例化上下文的类加载器
  2. 我的各种模型的类加载器类

如上所述,(1) 可以指定为 JAXBContext.newInstance() 中的参数,但仅当将模型指定为包名称而不是单个模型时 类。这意味着 JAXB 必须自己查找 类,它唯一可以用来执行此操作的类加载器是 (1) - 如果模型 类 散布,则无法看到所有模型跨多个捆绑包。

另一个线程 (Why can't JAXB find my jaxb.index when running inside Apache Felix?) 告诉我 JAXB 默认使用线程上下文类加载器来定位上下文实现。将它设置为一个可以看到实现(例如我自己的包的类加载器)的类加载器足以让 JAXB 找到它自己的实现,这让我可以自由地使用 类 的数组调用我喜欢的 newInstance() 版本。由于这些 类 已经加载,JAXB 可以按原样使用它们,而不必关心它们不同的类加载器。

简而言之:

Class[] myClasses = getModelClasses(); // gather all model classes, which may have different classloaders
Thread thread = Thread.currentThread();
ClassLoader originalClassLoader = thread.getContextClassLoader();
thread.setContextClassLoader(getClass().getClassLoader()); // my own bundle's classloader
JAXBContext context = JAXBContext.newInstance(myClasses);
thread.setContextClassLoader(originalClassLoader); // reset context classloader

上下文类加载器操作内容可以包装在 AutoCloseable 中,让我可以简单地将 JAXBContext 实例化包装在 try-with-resources 块中。