Путаница 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: Глядя на соответствующий код из трассировки стека, указанный загрузчик классов используется для:
- загрузить
ObjectFactory
- класса, расположенный в указанном пакетеcom.comp.gen
. - загрузить классы, указанные в
jaxb.index
файл, расположенный также в пакете.
// 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.класс!--22-->
- он должен содержать 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 класса решил проблему.