为什么在Apache Felix内部运行时,JAXB找不到我的jaxb.index文件?

55

它应该在要索引的包中,但当我调用时,它就在那里。

JAXBContext jc = JAXBContext.newInstance("my.package.name");

我遇到了一个JAXBException异常,提示说

"my.package.name"没有包含ObjectFactory.class或jaxb.index文件

尽管它们都已经存在于该包中。

虽然下面这个方法可以解决问题,但并不是我想要的:

JAXBContext jc = JAXBContext.newInstance(my.package.name.SomeClass.class);

这个问题在许多邮件列表和论坛中被很多人提出,但似乎没有得到答案。

我正在运行OpenJDK 6,所以我获取了源代码包并使用调试器进入库。它首先查找jaxb.properties文件,然后查找系统属性,如果两者都找不到,则尝试使用com.sun.internal.xml.bind.v2.ContextFactory创建默认上下文。在那里,异常会被抛出(在ContextFactor.createContext(String ClassLoader, Map)内),但我无法看到发生了什么,因为源代码不在此处。

ETA

根据ContentFactory的源代码,我在这里找到,这可能是无法按预期工作的代码段:

/**
 * Look for jaxb.index file in the specified package and load it's contents
 *
 * @param pkg package name to search in
 * @param classLoader ClassLoader to search in
 * @return a List of Class objects to load, null if there weren't any
 * @throws IOException if there is an error reading the index file
 * @throws JAXBException if there are any errors in the index file
 */
private static List<Class> loadIndexedClasses(String pkg, ClassLoader classLoader) throws IOException, JAXBException {
    final String resource = pkg.replace('.', '/') + "/jaxb.index";
    final InputStream resourceAsStream = classLoader.getResourceAsStream(resource);

    if (resourceAsStream == null) {
        return null;
    }

根据我的之前的经验,我猜测这与OSGi容器的类加载机制有关。不幸的是,我在这方面还有些力不从心。


我是说请发布异常堆栈跟踪。 - akarnokd
这篇文章已经有点长了,但我已经追踪到异常的起源,刚才已经发布在上面了。 - Hanno Fietz
10个回答

64

好的,这需要进行一些挖掘,但答案并不令人惊讶,也不是很复杂:

JAXB找不到jaxb.index,因为默认情况下newInstance(String)使用当前线程的类加载器(由Thread.getContextClassLoader()返回)。这在Felix内部无法工作,因为OSGi包和框架的线程具有单独的类加载器。

解决方案是从某处获取一个合适的类加载器,并使用newInstance(String, ClassLoader)。我从包含jaxb.index的包中的一个类中获取了一个合适的类加载器,出于灵活性考虑,也可能是一个明智的选择:ObjectFactory

ClassLoader cl = my.package.name.ObjectFactory.class.getClassLoader();
JAXBContext jc = JAXBContext.newInstance("my.package.name", cl);
也许您还可以获取Bundle实例正在使用的类加载器,但我无法弄清楚如何实现,而上述解决方案对我来说似乎是安全的。

1
实际上,在使用未设计为OSGi的库并对其获取的类加载器做出假设时,这通常会成为OSGi环境中相当棘手的问题。这个问题是人们声称Eclipselink是唯一在OSGi中工作的JPA提供程序的原因(不知道现在是否仍然如此)。 - Hanno Fietz
1
ContextClassLoader;设置类加载器时,应首先检查是否已经设置了一个。如果是这样,在调用JAX之后将其保存为局部变量,然后在finally块中重置它-您不知道还有什么其他使用类加载器的技巧... - earcam
这个问题是否与Jira有关联?我们在这里遇到了困难,但我可以证明这个解决方案是有效的。我只是想知道这是否是apache-felix项目中一个已经认可的问题。 - Monachus
@Monachus - 这并不是一个Felix问题,更多的是OSGi 总体上 会为那些突然在OSGi环境中使用而从未为其设计的库创建这种类型的问题。这实际上是有意设计的(OSGi很大程度上是关于分离类加载器),因此这不是OSGi容器可以或者甚至应该尝试修复的东西。 - Hanno Fietz
谢谢!稍作优化: Class<ObjectFactory> c = my.package.name.ObjectFactory.class; ClassLoader cl = c.getClassLoader(); JAXBContext jc = JAXBContext.newInstance(c.getPackage().getName(), cl);这样你就不会因为重命名包而遇到问题了。 - Morrandir
@HannoFietz 我有一个类似的问题,并尝试使用您的代码,但我没有jaxb.index文件,我该如何在类中获得它?https://dev59.com/MFsX5IYBdhLWcg3wALXa - Jack

6

我是一位有用的助手,可以为您翻译文本。

我遇到了一个类似的问题,因为它与我正在工作的项目有关。阅读http://jaxb.java.net/faq/index.html#classloader后,我意识到JAXBContext无法找到包含jaxb.index的包。

我将尽力让这个问题更加清晰易懂。

我们有:

Bundle A
   -- com.a
      A.java
        aMethod()
        {
            B.bMethod("com.c.C");
        }
MANIFEST.MF
Import-Package: com.b, com.c         

Bundle B
   -- com.b
      B.java
        bmethod(String className)
        {
            Class clazz = Class.forName(className);
        }

Export-Package: com.b

Bundle C
   -- com.c
      C.java
        c()
        {
            System.out.println("hello i am C");
        }

Export-Package: com.c

与JAXB相关。B类是JAXBContext,bMethod是newInstance()。
如果您熟悉OSGi包限制,那么现在很清楚Bundle B没有导入com.c包,即类C对类B不可见,因此它无法实例化C。
解决方案是将ClassLoader传递给bMethod。这个ClassLoader应该来自导入com.c的bundle。在这种情况下,我们可以传递A.class.getClassLoader(),因为bundle A正在导入com.c。
希望这有所帮助。

4

对于相同的问题,我通过手动将包放入导入中来解决它。


1
如果您的项目中使用了Maven,那么只需使用这个库:
<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-osgi</artifactId>
    <version>2.2.7</version>
</dependency>

这个库是为Glasfish服务器创建的,但也可以在Tomcat上使用(已检查过)。 使用这个库,您可以轻松地将JAXB与OSGI捆绑包一起使用。


0
对我来说,问题是一个与我开发的模块无关的单元测试在其pom.xml文件中没有将我的模块作为依赖项。该UT仍然可以识别我的模块,因为它从共享配置文件中获取软件包列表。
在运行UT时,它没有编译新模块,因此它没有生成ObjectFactory.java,即使我编译模块时能够看到ObjectFactory.java,我仍然会收到错误信息。
添加了以下依赖项:
<dependency>
    <groupId>com.myCompany</groupId>
    <artifactId>my-module-name</artifactId>
    <version>${project.version}</version>
    <scope>test</scope>
</dependency>

0

可能存在另一种情况会导致这个问题。

当您安装并启动一个导出包含jaxb.index或objectFactory.java的包的bundle时

然后请确保导入类的bundles已停止或指向正确的包名称。

还要检查pom.xml中的导出和导入语句

在servicemix(karaf)osgi容器中遇到了类似的问题


0

我成功解决了这个问题,方法是将包含ObjectFactory的生成类的包添加到我的捆绑定义的<Private-Package>部分中,还有org.jvnet.jaxb2_commons.*


0

编辑2:

我曾经在我的应用程序中遇到过类似的奇怪的类加载问题。如果我将其作为普通应用程序运行,一切都正常,但是当我将其作为Windows服务调用时,它开始出现ClassNotFoundExceptions错误。分析显示线程的类加载器以某种方式为空。我通过在线程上设置SystemClassLoader来解决了这个问题:

// ...
thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
thread.start();
// ...

不知道你的容器是否允许这种更改。


嗯,可能吧(我不确定),但在我看来,应该有一个地方可以放置文件,以便使用的类加载器可以找到它。 - Hanno Fietz

0

我刚遇到了这个问题。对我来说,解决方案是使用IBM的JRE而不是Oracle的。看起来在那个版本中,JAXB实现更加适合OSGI。


-1

我的解决方案是:

JAXBContext context = JAXBContext.newInstance(new Class[]{"my.package.name"});

或者

JAXBContext context = JAXBContext.newInstance(new Class[]{class.getName()});

或者

一个完整的解决方案:

public static <T> T deserializeFile(Class<T> _class, String _xml) {

        try {

            JAXBContext context = JAXBContext.newInstance(new Class[]{_class});
            Unmarshaller um = context.createUnmarshaller();

            File file = new File(_xml);
            Object obj = um.unmarshal(file);

            return _class.cast(obj);

        } catch (JAXBException exc) {
            return null;
        }
    }

百分之百有效


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接