使用 packageName 和 Classloader 实例化 JAXBContext 时混淆 ClassNotFoundException
Confusing ClassNotFoundException when instantiating JAXBContext with packageName and Classloader
我正在尝试使用 java 中的 JAXB 将 XML 文件解组为生成的 class 结构。我 运行 遇到了一个令人困惑的问题,我交给 JAXBContext.newInstance(packageName, classLoader)
的 classloader 显然找不到一些必要的 classes 来实例化模式 classes,但是当我手动搜索提供的 classloader 以查找所需的 classes 时,它们就在那里:
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,加载生成的 classes 所需的一些特定 jar,然后将系统 classloader 设置为父类。生成的 classes 使用一些我放入 classloader 的 postgresql 库,这是我遇到问题的资源。 JAXB 在提供的包中正确地找到了 ObjectFactory class,问题似乎只是生成的 classes 本身的实例化。
运行 这段代码的结果是 cl.loadClass("org.postgresql.util.PGInterval");
的手动调用工作正常,它记录了第一条声明它找到 class 的语句,没有抛出异常。但是当 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 文档支持这一点)它会使用提供的 classloader 来查找实例化 classes 所需的实现 classes,因此鉴于资源似乎在我提供的 classloader 中,为什么 JAXB 找不到它?
编辑:
添加使用 PGInterval 资源的生成 class 的相关部分:
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;
}
我想值得注意的是,这是生成的 class 中唯一的导入,它不在 java 的标准库中。
根据 this FAQ,当在应用程序容器和服务器中使用 JAXB 时,可能会发生 class 加载问题。建议的解决方案是在创建 JAXBContext
:
时使用当前的 class 加载器
JAXBContext.newInstance( "com.comp.gen", this.getClass().getClassLoader() );
编辑:
通过查看堆栈跟踪中的相关代码,指定的 classloader 用于:
- 加载位于指定包
com.comp.gen
中的ObjectFactory
class。
- 加载
jaxb.index
文件中指定的 classes,该文件也位于包中。
// 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 似乎使用某种反射从这些已加载的 classes 中加载所有静态可达的 classes。这也是Javadocs of JAXBContext#newInstance(String contextPath, ClassLoader classLoader)
中提到的:
Every package listed on the contextPath must meet one or both of the following conditions otherwise a JAXBException will be thrown:
- it must contain ObjectFactory.class
- it must contain jaxb.index
Format for jaxb.index
The file contains a newline-separated list of class names. Space and tab characters, as well as blank lines, are ignored. The comment character is '#' (0x23); on each line all characters following the first comment character are ignored. The file must be encoded in UTF-8. Classes that are reachable, as defined in newInstance(Class...)
, from the listed classes are also registered with JAXBContext.
我假设(但这是我不确定的部分...)所有可访问的 classes 也将使用您提供的 classloader 加载。但很明显,在参考路径的某个地方,org.postgresql.util.PGInterval
是 而不是 被那个 classloader 加载的。如果引用 org.postgresql.util.PGInterval
的 class 本身不是由您的自定义 classloader 加载,而是由父(系统)classloader 加载,则可能是这种情况。这意味着您可能希望确保您的自定义 classloader 能够加载从顶级 class 到 org.postgresql.util.PGInterval
class 的所有 classes .
所以我最终弄明白了。在我工作的代码库深处,一位先前的开发人员创建了一个自定义动态 classloader,它使用 ClassLoader.getSystemClassLoader() 作为其父级。这个自定义 classloader 实际上用于从磁盘加载生成的 JAXB class,我使用该实例的 classloader 来加载 JAXB 内容。一旦我看到,我立即知道问题所在。
我 运行 所在的环境是 tomcat 之上的 restapi,因此我的组件级别的系统-classloader 仅包含 bootstrap tomcat.
之前的开发人员只在独立的 JVM 中 运行 他的代码,他在其中提供了一个巨大的 class 路径。因此,尽管这在技术上是一个环境问题,但根本原因是使用 ClassLoader.getSystemClassLoader() 作为新 ClassLoader 的父类。将父级更改为更符合逻辑的东西,即包含 class' classloader 解决了问题。
我正在尝试使用 java 中的 JAXB 将 XML 文件解组为生成的 class 结构。我 运行 遇到了一个令人困惑的问题,我交给 JAXBContext.newInstance(packageName, classLoader)
的 classloader 显然找不到一些必要的 classes 来实例化模式 classes,但是当我手动搜索提供的 classloader 以查找所需的 classes 时,它们就在那里:
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,加载生成的 classes 所需的一些特定 jar,然后将系统 classloader 设置为父类。生成的 classes 使用一些我放入 classloader 的 postgresql 库,这是我遇到问题的资源。 JAXB 在提供的包中正确地找到了 ObjectFactory class,问题似乎只是生成的 classes 本身的实例化。
运行 这段代码的结果是 cl.loadClass("org.postgresql.util.PGInterval");
的手动调用工作正常,它记录了第一条声明它找到 class 的语句,没有抛出异常。但是当 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 文档支持这一点)它会使用提供的 classloader 来查找实例化 classes 所需的实现 classes,因此鉴于资源似乎在我提供的 classloader 中,为什么 JAXB 找不到它?
编辑: 添加使用 PGInterval 资源的生成 class 的相关部分:
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;
}
我想值得注意的是,这是生成的 class 中唯一的导入,它不在 java 的标准库中。
根据 this FAQ,当在应用程序容器和服务器中使用 JAXB 时,可能会发生 class 加载问题。建议的解决方案是在创建 JAXBContext
:
JAXBContext.newInstance( "com.comp.gen", this.getClass().getClassLoader() );
编辑: 通过查看堆栈跟踪中的相关代码,指定的 classloader 用于:
- 加载位于指定包
com.comp.gen
中的ObjectFactory
class。 - 加载
jaxb.index
文件中指定的 classes,该文件也位于包中。
// 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 似乎使用某种反射从这些已加载的 classes 中加载所有静态可达的 classes。这也是Javadocs of JAXBContext#newInstance(String contextPath, ClassLoader classLoader)
中提到的:
Every package listed on the contextPath must meet one or both of the following conditions otherwise a JAXBException will be thrown:
- it must contain ObjectFactory.class
- it must contain jaxb.index
Format for jaxb.index
The file contains a newline-separated list of class names. Space and tab characters, as well as blank lines, are ignored. The comment character is '#' (0x23); on each line all characters following the first comment character are ignored. The file must be encoded in UTF-8. Classes that are reachable, as defined in
newInstance(Class...)
, from the listed classes are also registered with JAXBContext.
我假设(但这是我不确定的部分...)所有可访问的 classes 也将使用您提供的 classloader 加载。但很明显,在参考路径的某个地方,org.postgresql.util.PGInterval
是 而不是 被那个 classloader 加载的。如果引用 org.postgresql.util.PGInterval
的 class 本身不是由您的自定义 classloader 加载,而是由父(系统)classloader 加载,则可能是这种情况。这意味着您可能希望确保您的自定义 classloader 能够加载从顶级 class 到 org.postgresql.util.PGInterval
class 的所有 classes .
所以我最终弄明白了。在我工作的代码库深处,一位先前的开发人员创建了一个自定义动态 classloader,它使用 ClassLoader.getSystemClassLoader() 作为其父级。这个自定义 classloader 实际上用于从磁盘加载生成的 JAXB class,我使用该实例的 classloader 来加载 JAXB 内容。一旦我看到,我立即知道问题所在。
我 运行 所在的环境是 tomcat 之上的 restapi,因此我的组件级别的系统-classloader 仅包含 bootstrap tomcat.
之前的开发人员只在独立的 JVM 中 运行 他的代码,他在其中提供了一个巨大的 class 路径。因此,尽管这在技术上是一个环境问题,但根本原因是使用 ClassLoader.getSystemClassLoader() 作为新 ClassLoader 的父类。将父级更改为更符合逻辑的东西,即包含 class' classloader 解决了问题。