Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confusing ClassNotFoundException when instantiating JAXBContext with packageName and Classloader

I'm trying to unmarshall an XML file into a generated class structure using JAXB in java. I'm running into a perplexing issue where the classloader I hand into JAXBContext.newInstance(packageName, classLoader) can apparently not find some of the necessary classes to instantiate the schema classes, but when I manually search the supplied classloader for the needed classes, they are there:

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);

The getJaxbClassloader() method just creates a new URLClassLoader, loading up some specific jars needed by the generated classes, and then setting the system classloader as the parent. The generated classes use some postgresql libraries that I put into the classloader, which is the resource I'm having the issue with. JAXB correctly finds the ObjectFactory class in the supplied package, it's just the instantiation of the generated classes themselves that seem be the problem.

The result of running this code is that the manual call of cl.loadClass("org.postgresql.util.PGInterval"); works fine, it logs the the first statement saying that it found the class, no exceptions thrown. But when the JAXBContext is instantiated, it throws a CNFE on the exact same resource:

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

More Thorough stack trace:

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)

Anyone have an idea as to what's going wrong here? I was under the impression (and the JAXBContext docs support this) that it would use the supplied classloader to find an implementation classes needed to instantiate the classes, so given that the resource appears to be within the classloader I'm supplying, why is JAXB unable to find it?

EDIT: Adding the relevant portion of the generated class that uses the PGInterval resource:

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;
}

I guess it's worth noting that this this the only import in the generated class is that is not in java's standard library.

like image 359
user3062946 Avatar asked Jun 18 '15 13:06

user3062946


1 Answers

According to this FAQ, class loading issues can happen when JAXB is used in application containers and servers. The suggested solution is to use the current class loader when creating a JAXBContext:

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

EDIT: By looking at the relevant code from the stack trace, the specified classloader is used to:

  1. load the ObjectFactory class located in the specified package com.comp.gen.
  2. load the classes specified in the jaxb.index file located also in the package.

cf. 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) {
    ...

From then on it seems JAXB uses some sort of reflection to load all statically reachable classes from these already loaded classes. This is what is also mentioned in the 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:

  1. it must contain ObjectFactory.class
  2. 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.

I assume (but this is the part I'm not sure of...) that all reachable classes would also be loaded using the classloader you provided. But obviously somewhere down the reference paths, the org.postgresql.util.PGInterval is not being loaded by that classloader. This may be the case if the class that references org.postgresql.util.PGInterval was itself not loaded by your custom classloader but by the parent (system) classloader. This means you may want to make sure your custom classloader is able to load all classes from the top-level class down to the org.postgresql.util.PGInterval class.

like image 114
M A Avatar answered Oct 01 '22 17:10

M A