Путаница 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);

The getJaxbClassloader() метод просто создает новый URLClassLoader, загружая некоторые конкретные банки, необходимые сгенерированным классы, а затем установка system classloader в качестве родительского. Сгенерированные классы используют некоторые библиотеки postgresql, которые я помещаю в classloader, который является ресурсом, с которым у меня проблема. 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 поддерживают это), что он будет использовать предоставленный classloader для поиска классов реализации, необходимых для создания экземпляров классов, поэтому, учитывая, что ресурс, похоже, находится в пределах classloader я поставляю, почему 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 ответов


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

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

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

  1. загрузить ObjectFactory - класса, расположенный в указанном пакете com.comp.gen.
  2. загрузить классы, указанные в jaxb.index файл, расположенный также в пакете.

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

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

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

  1. он должен содержать ObjectFactory.класс!--22-->
  2. он должен содержать jaxb.индекс

формат для jaxb.индекс

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

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


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

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

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