使用packageName和Classloader

时间:2015-06-18 13:41:46

标签: 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);

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的标准库。

2 个答案:

答案 0 :(得分:5)

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

时使用当前的类加载器
JAXBContext.newInstance( "com.comp.gen", this.getClass().getClassLoader() );

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

  1. 加载位于指定包ObjectFactory中的com.comp.gen类。
  2. 加载位于包中的jaxb.index文件中指定的类。
  3. 比照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) {
        ...
    

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

      

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

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

      jaxb.in​​dex的格式

           

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

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

答案 1 :(得分:3)

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

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

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