Путаница ClassNotFoundException при создании экземпляра JAXBContext с именем packageName и Classloader

Я пытаюсь размонтировать XML файл в сгенерированную структуру классов, используя JAXB в java. Я сталкиваюсь с недоумением, когда загрузчик классов, который я передаю в 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, загружая некоторые конкретные банки, необходимые для сгенерированных классов, а затем устанавливает системный загрузчик классов как родительский. Сгенерированные классы используют некоторые библиотеки 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 docs поддерживают это), что он будет использовать предоставленный загрузчик классов для поиска классов реализации, необходимых для создания экземпляров классов, поэтому, учитывая, что ресурс, как представляется, находится в загрузчике классов, который я поставляю, почему JAXB не удалось найти его?

EDIT: Добавление соответствующей части сгенерированного класса, использующего ресурс 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.

Ответ 1

Поэтому я в конце концов понял это. Глубоко в базе кода, в которой я работал, предыдущий разработчик создал собственный динамический загрузчик классов, который использовал ClassLoader.getSystemClassLoader() в качестве родителя. Этот пользовательский загрузчик классов фактически используется для загрузки сгенерированного класса JAXB с диска, и я использовал этот загрузчик классов экземпляра для JAXB. Однажды я увидел, что сразу понял эту проблему.

В среде, в которой я работал, был restapi поверх tomcat, поэтому системный загрузчик на моем уровне компонента содержал только каталиновые банки, необходимые для загрузки tomcat.

Предыдущий разработчик только когда-либо запускал свой код в автономной JVM, где он предоставил огромный путь к классам. Поэтому, хотя это была технически экологическая проблема, основной причиной было использование ClassLoader.getSystemClassLoader() в качестве родителя нового ClassLoader. Изменение родительского элемента было чем-то более логичным, т.е. Класс-загрузчик классов решил проблему.

Ответ 2

Согласно этим часто задаваемым вопросам, проблемы с загрузкой классов могут возникать, когда JAXB используется в контейнерах приложений и серверах. Предлагаемое решение заключается в использовании текущего загрузчика классов при создании JAXBContext:

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

EDIT: Просмотрев соответствующий код из трассировки стека, указанный загрузчик классов используется для:

  • загрузите класс ObjectFactory, расположенный в указанном пакете com.comp.gen.
  • загрузите классы, указанные в файле 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) {
    ...

С этого момента кажется, что JAXB использует какое-то отражение для загрузки всех статически доступных классов из уже загруженных классов. Это также упоминается в Javadocs JAXBContext#newInstance(String contextPath, ClassLoader classLoader):

Каждый пакет, указанный в contextPath, должен удовлетворять одному или двум из следующих условий, иначе будет выведено исключение JAXBException:

  • он должен содержать ObjectFactory.class
  • он должен содержать jaxb.index

Формат для jaxb.index

Файл содержит список имен классов, разделенных символом новой строки. Символы пробела и табуляции, а также пустые строки игнорируются. Символ комментария - "#" (0x23); в каждой строке игнорируются все символы, следующие за первым символом комментария. Файл должен быть закодирован в UTF-8. Классы, которые достижимы, как определено в newInstance(Class...), из перечисленных классов также регистрируются в JAXBContext.

Я предполагаю (но это та часть, в которой я не уверен...), что все доступные классы также будут загружаться с помощью предоставленного вами загрузчика классов. Но, очевидно, где-то ниже ссылочных путей, org.postgresql.util.PGInterval не загружается этим загрузчиком классов. Это может иметь место, если класс, который ссылается на org.postgresql.util.PGInterval, сам не загружается вашим пользовательским загрузчиком классов, а родительским (системным) загрузчиком классов. Это означает, что вы можете захотеть, чтобы ваш пользовательский загрузчик классов мог загружать все классы из класса верхнего уровня в класс org.postgresql.util.PGInterval.