使用packageName和Classloader实例化JAXBContext时,会混淆ClassNotFoundException

use*_*946 11 java jaxb classloader

我正在尝试使用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);
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

更彻底的堆栈跟踪:

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)
Run Code Online (Sandbox Code Playgroud)

任何人都知道这里出了什么问题?我的印象是(并且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;
}
Run Code Online (Sandbox Code Playgroud)

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

man*_*uti 5

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

JAXBContext.newInstance( "com.comp.gen", this.getClass().getClassLoader() );
Run Code Online (Sandbox Code Playgroud)

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

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

比照 com.sun.xml.internal.bind.v2.ContextFactory#createContext(String contextPath, ClassLoader classLoader, Map<String,Object> 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) {
    ...
Run Code Online (Sandbox Code Playgroud)

从那时起,似乎JAXB使用某种反射来从这些已经加载的类加载所有静态可达的类.这也是Javadocs中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.PGInterval被该类加载器加载.如果引用的类org.postgresql.util.PGInterval本身未由您的自定义类加载器加载,而是由父(系统)类加载器加载,则可能就是这种情况.这意味着您可能希望确保自定义类加载器能够将所有类从顶级类加载到org.postgresql.util.PGInterval类.


use*_*946 4

所以我最终明白了这一点。在我工作的代码库深处,一位先前的开发人员创建了一个自定义动态类加载器,该类加载器使用 ClassLoader.getSystemClassLoader() 作为其父级。这个自定义类加载器实际上用于从磁盘加载生成的 JAXB 类,并且我将该实例的类加载器用于 JAXB 内容。我一看就知道问题所在了。

我运行的环境是tomcat之上的restapi,因此我的组件级别的系统类加载器仅包含引导tomcat所需的catalina jar。

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