【问题标题】:Confusing ClassNotFoundException when instantiating JAXBContext with packageName and Classloader使用 packageName 和 Classloader 实例化 JAXBContext 时混淆 ClassNotFoundException
【发布时间】:2015-09-04 04:36:08
【问题描述】:

我正在尝试使用 Java 中的 JAXB 将 XML 文件解组为生成的类结构。我遇到了一个令人困惑的问题,我交给JAXBContext.newInstance(packageName, classLoader) 的类加载器显然找不到一些必要的类来实例化模式类,但是当我手动搜索提供的类加载器以查找所需的类时,它们就在那里:

URLClassLoader cl = this.getJaxbClassloader();
try 
{
    cl.loadClass("org.postgresql.util.PGInterval");
    Log.error("Found class [" + name + "] in provided classloader");
} 
catch (ClassNotFoundException e) 
{
    Log.error("Unable to find class [" + name + "]  in provided classloader");
}

JAXBContext ctx = JAXBContext.newInstance( "com.comp.gen", cl);

getJaxbClassloader() 方法只是创建了一个新的 URLClassLoader,加载了生成的类需要的一些特定的 jar,然后将系统类加载器设置为父级。生成的类使用我放入类加载器的一些 postgresql 库,这是我遇到问题的资源。 JAXB 在提供的包中正确找到了 ObjectFactory 类,问题似乎只是生成的类本身的实例化。

运行此代码的结果是手动调用cl.loadClass("org.postgresql.util.PGInterval"); 工作正常,它记录第一条语句说它找到了类,没有抛出异常。但是当 JAXBContext 被实例化时,它会在完全相同的资源上抛出一个 CNFE:

java.lang.ClassNotFoundException: org.postgresql.util.PGInterval
   at java.net.URLClassLoader.findClass(URLClassLoader.java:600)
   at java.lang.ClassLoader.loadClassHelper(ClassLoader.java:772)
   at java.lang.ClassLoader.loadClass(ClassLoader.java:745)
   at java.lang.ClassLoader.loadClass(ClassLoader.java:726)
   ... 78 more

更彻底的堆栈跟踪:

java.lang.NoClassDefFoundError: org.postgresql.util.PGInterval
    at java.lang.Class.getDeclaredFieldsImpl(Native Method)
    at java.lang.Class.getDeclaredFields(Class.java:740)
    at com.sun.xml.bind.v2.model.nav.ReflectionNavigator.getDeclaredFields(ReflectionNavigator.java:249)
    at com.sun.xml.bind.v2.model.nav.ReflectionNavigator.getDeclaredFields(ReflectionNavigator.java:58)
    at com.sun.xml.bind.v2.model.impl.ClassInfoImpl.findFieldProperties(ClassInfoImpl.java:370)
    at com.sun.xml.bind.v2.model.impl.RuntimeClassInfoImpl.getProperties(RuntimeClassInfoImpl.java:176)
    at com.sun.xml.bind.v2.model.impl.ModelBuilder.getClassInfo(ModelBuilder.java:243)
    at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.getClassInfo(RuntimeModelBuilder.java:100)
    at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.getClassInfo(RuntimeModelBuilder.java:81)
    at com.sun.xml.bind.v2.model.impl.ModelBuilder.getClassInfo(ModelBuilder.java:209)
    at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.getClassInfo(RuntimeModelBuilder.java:95)
    at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.getClassInfo(RuntimeModelBuilder.java:81)
    at com.sun.xml.bind.v2.model.impl.ModelBuilder.getTypeInfo(ModelBuilder.java:315)
    at com.sun.xml.bind.v2.model.impl.RegistryInfoImpl.<init>(RegistryInfoImpl.java:99)
    at com.sun.xml.bind.v2.model.impl.ModelBuilder.addRegistry(ModelBuilder.java:357)
    at com.sun.xml.bind.v2.model.impl.ModelBuilder.getTypeInfo(ModelBuilder.java:327)
    at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:466)
    at com.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:302)
    at com.sun.xml.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1136)
    at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:154)
    at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:121)
    at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:202)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:95)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:56)
    at java.lang.reflect.Method.invoke(Method.java:620)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:184)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:144)
    at javax.xml.bind.ContextFinder.find(ContextFinder.java:346)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:443)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:406)

有人知道这里出了什么问题吗?我的印象是(并且 JAXBContext 文档支持这一点)它会使用提供的类加载器来查找实例化类所需的实现类,因此鉴于资源似乎在我提供的类加载器中,为什么是 JAXB找不到?

编辑: 添加使用 PGInterval 资源的生成类的相关部分:

import org.postgresql.util.PGInterval;
...
... 
...
@XmlElement(name = "time_to_live", required=false)
protected PGInterval time_to_live;

public PGInterval gettime_to_live()
{
    return time_to_live;
}

public void settime_to_live(PGInterval time_to_live)
{
    this.time_to_live = time_to_live;
}

我想值得注意的是,生成的类中唯一的导入不在 java 的标准库中。

【问题讨论】:

  • 确实很有趣。你能分享一下有 PGInterval 属性的类吗?
  • 不可能发布代码,生成的类是巨大的(不是按照我的设计我可能会添加......我正在使用其他人的代码库)。但是我可以找出使用 PGInterval 的 sn-ps。
  • 让我们检查一下我们是否也可以这样做cl.loadClass("org.postgresql.util.PGInterval").getDeclaredFields(); 问题可能是 PGInterval 本身。它似乎试图获取 PGInterval 的字段。
  • 抱歉,回复晚了,但该电话工作正常。获取 PGInterval 类的声明字段有效。
  • 你有什么理由不打电话给JAXBContext.newInstance(TopLevelClass.class)吗?如果顶级类静态到达PGInterval 类应该没问题。你也可以试试JAXBContext.newInstance("com.comp.gen", TopLevelClass.class.getClassLoader())

标签: java jaxb classloader


【解决方案1】:

根据this FAQ,在应用程序容器和服务器中使用 JAXB 时可能会出现类加载问题。建议的解决方案是在创建JAXBContext 时使用当前的类加载器:

JAXBContext.newInstance( "com.comp.gen", this.getClass().getClassLoader() );

编辑: 通过查看堆栈跟踪中的相关代码,指定的类加载器用于:

  1. 加载位于指定包com.comp.gen中的ObjectFactory类。
  2. 加载同样位于包中的jaxb.index 文件中指定的类。

参见。 com.sun.xml.internal.bind.v2.ContextFactory#createContext(String contextPath, ClassLoader classLoader, Map&lt;String,Object&gt; properties):

// look for ObjectFactory and load it
final Class<?> o;
try {
    o = classLoader.loadClass(pkg+".ObjectFactory");
    classes.add(o);
    ...

// look for jaxb.index and load the list of classes
try {
    indexedClasses = loadIndexedClasses(pkg, classLoader);
} catch (IOException e) {
    ...

从那时起,JAXB 似乎使用某种反射来从这些已加载的类中加载所有静态可访问的类。这也是Javadocs of JAXBContext#newInstance(String contextPath, ClassLoader classLoader)中提到的:

contextPath 中列出的每个包都必须满足以下一个或两个条件,否则将抛出 JAXBException:

  1. 它必须包含 ObjectFactory.class
  2. 它必须包含 jaxb.in​​dex

jaxb.in​​dex 的格式

该文件包含一个以换行符分隔的类名列表。空格和制表符以及空白行将被忽略。注释字符是'#' (0x23);在每一行中,第一个注释字符之后的所有字符都将被忽略。该文件必须以 UTF-8 编码。 newInstance(Class...) 中定义的所列类中可访问的类也注册到 JAXBContext

我假设(但这是我不确定的部分......)所有可访问的类也将使用您提供的类加载器加载。但显然在引用路径的某个地方,org.postgresql.util.PGIntervalnot 由该类加载器加载的。如果引用 org.postgresql.util.PGInterval 的类本身不是由您的自定义类加载器加载,而是由父(系统)类加载器加载,则可能是这种情况。这意味着您可能希望确保您的自定义类加载器能够加载从顶级类到org.postgresql.util.PGInterval 类的所有类。

【讨论】:

  • 感谢您的建议。我实际上已经尝试过了,但这并不是一个真正的选择,因为 JAXB 类本身是动态生成的,而不是在默认类加载器的类路径上生成的。如果我使用类的类加载器,它根本无法在所需的包中找到 ObjectFactory 并失败。我需要在某些方面使用生成的类的类加载器,以便它可以找到 ObjectFactory。
  • @user3062946 那么在这种情况下使用GeneratedJAXBClass.class.getClassLoader() 工作吗?
  • 不,这正是问题所在。该类加载器不包含所需的 PGInterval 资源。所以我创建了一个新的类加载器,指向 PGInterval jar 的 URL,然后将 GeneratedJAXBClass.class.getClassLoader() 设置为其父级。但是 JAXB 只是说它无论如何都找不到 PGInterval
  • 您好,感谢您提供的额外信息。我不太确定这一切。我确保最初从磁盘加载GeneratedJAXBClass.class 的类加载器也有PGInterval 可用,但仍然没有变化。就像我之前说的,在自己的 JVM 中运行组件可以解决问题,但这并不是我真正想要的。我需要能够从一个宁静的 api 在 tomcat 中运行它。看起来好像应用服务器/框架的类加载器声明在某种程度上与 JAXB 类加载混淆了......
【解决方案2】:

所以我最终想通了。在我工作的代码库的深处,一位之前的开发人员创建了一个自定义动态类加载器,它使用 ClassLoader.getSystemClassLoader() 作为其父级。这个自定义类加载器实际上用于从磁盘加载生成的 JAXB 类,我使用该实例的类加载器来加载 JAXB 内容。一旦我看到,我立即知道问题所在。

我运行的环境是一个基于 tomcat 的 restapi,所以我的组件级别的系统类加载器只包含引导 tomcat 所需的 catalina jar。

之前的开发人员只在独立的 JVM 中运行他的代码,他提供了一个巨大的类路径。所以虽然这在技术上是一个环境问题,但根本原因是使用 ClassLoader.getSystemClassLoader() 作为新 ClassLoader 的父级。将父级更改为更合乎逻辑的东西,即包含类的类加载器解决了这个问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-01
    • 2011-12-27
    • 2020-10-07
    • 1970-01-01
    相关资源
    最近更新 更多