Поля маршалинга JAXB, тип которых является параметром универсального типа

Я ожидаю, что следующий тест будет работать с Sun JAXB RI 2.2.1.1, но вместо этого он завершается с ошибкой NullPointerException при построении JAXBContext:

public class GenericFieldMarshallTest {

    public static class CustomType {
    }

    public static class CustomTypeAdapter extends XmlAdapter<String, CustomType> {
        @Override
        public String marshal(CustomType v) throws Exception {
            return "CustomType";
        }
        @Override
        public CustomType unmarshal(String v) throws Exception {
            return new CustomType();
        }
    }

    @XmlJavaTypeAdapter(type = CustomType.class, value = CustomTypeAdapter.class)
    public static class RootElement<ValueType> {
        @XmlValue public ValueType value;
    }

    @XmlRootElement(name = "root")
    public static class CustomRootElement extends RootElement<CustomType> {
        public CustomRootElement() {
            value = new CustomType();
        }
    }

    @Test
    public void test() throws Exception {
        JAXBContext context = JAXBContext.newInstance(CustomRootElement.class,
                CustomType.class, RootElement.class);
        StringWriter w = new StringWriter();
        context.createMarshaller().marshal(new CustomRootElement(), w);
        assertThat(w.toString(), equalTo("<root>CustomType</root>"));
    }

}

Исключение, которое я получаю:

java.lang.NullPointerException
        at com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor.get(TransducedAccessor.java:165)
        at com.sun.xml.bind.v2.runtime.property.ValueProperty.<init>(ValueProperty.java:77)
        at com.sun.xml.bind.v2.runtime.property.PropertyFactory.create(PropertyFactory.java:106)
        at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.<init>(ClassBeanInfoImpl.java:179)
        at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:515)
        at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.<init>(ClassBeanInfoImpl.java:166)
        at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:515)
        at com.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:330)
        at com.sun.xml.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1140)
        at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:154)
        at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:121)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:202)
        at javax.xml.bind.ContextFinder.find(ContextFinder.java:363)
        at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)
        at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:522)

Причина, по-видимому, в том, что JAXB не знает, как маршалировать объявленный тип моего поля (который, я думаю, стирается в Object во время выполнения?), хотя во время выполнения я только когда-либо устанавливал для поля типы, о которых JAXB знает.

Как я могу маршалировать поле универсального типа?

(Замена @XmlValue на @XmlAttribute не устраняет исключение, равно как и изменение объявленного типа поля на Object, хотя, конечно, все работает нормально, если поле объявлено как String, за исключением того, что String нельзя назначить из CustomType. Размещение @XmlJavaTypeAdapter также не имеет значения; в моем реальном коде он устанавливается на уровне пакета в package-info.java.)


person DanC    schedule 29.06.2010    source источник


Ответы (1)


Во-первых: ваш XmlAdapter неверен. С универсальными типами дело обстоит наоборот.

Тогда вам, похоже, придется поставить @XmlJavaTypeAdapter на CustomRootElement.

Кроме того, JAXBContext необходимо рассказать обо всех задействованных классах. Либо создайте jaxb.index, либо ObjectFactory и создайте контекст, задав имя пакета методу newInstance, либо перечислите все классы.

Полный код (слегка измененный, так как я использую main(), а не метод тестирования JUnit):

public static class CustomType {
}

public static class CustomTypeAdapter extends
        XmlAdapter<String, CustomType> {

    @Override
    public String marshal(CustomType v) throws Exception {
        return "CustomType";
    }

    @Override
    public CustomType unmarshal(String v) throws Exception {
        return new CustomType();
    }

}

public static class RootElement<V> {
    public V value;
}

@XmlJavaTypeAdapter(CustomTypeAdapter.class)
@XmlRootElement(name = "root")
public static class CustomRootElement extends RootElement<CustomType> {
    public CustomRootElement() {
        value = new CustomType();
    }
}

public static void main(String[] args) throws Exception {
    JAXBContext context = JAXBContext.newInstance(CustomRootElement.class,
            CustomType.class, RootElement.class);
    StringWriter w = new StringWriter();
    CustomRootElement cre = new CustomRootElement();
    cre.value = new CustomType();

    Marshaller marshaller = context.createMarshaller();
    marshaller.setProperty("jaxb.formatted.output", Boolean.TRUE);
    marshaller.marshal(cre, w);

    System.err.println(w.toString());

    // just to see whether unmarshalling works too
    CustomRootElement c = (CustomRootElement) context.createUnmarshaller()
            .unmarshal(new StringReader(w.toString()));
    System.err.println(c.value);
}

Теперь результат сортировки CustomRootElement отличается от того, что вы ожидаете в своем тесте (и это не то, что я ожидал), но вы можете разобрать его и получить то, что вы сортировали ранее. Таким образом (не)сортировка работает, но XML выглядит не так хорошо:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <value xsi:type="customType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</root>

Я также добавил поле в CustomType, и это тоже сработало. Поэтому, если вам не нужен хороший XML, этого решения должно быть достаточно. Надеюсь, я не забыл никаких изменений, которые я сделал. Если да, просто оставьте комментарий, и я отредактирую соответственно.

person musiKk    schedule 29.06.2010
comment
Вы правы насчет порядка типов XmlAdapter, я хотел, чтобы они были наоборот. Это была ошибка транскрипции, когда я написал поддельный пример для своего вопроса. Я исправил это в своем вопросе. Однако ни одно из остальных ваших предложений не приводит к рабочему коду для меня. Размещение аннотации @XmlJavaTypeAdapter является произвольным; в моем реальном коде это находится на уровне пакета в package-info.class. Я попытался добавить другие классы, участвующие в вызове JAXBContext.newInstance() на случай, если JAXB не сможет их найти сам, но это также не имеет значения для проблемы. - person DanC; 29.06.2010
comment
Если у вас есть рабочая версия теста в моем вопросе, не могли бы вы поместить ее в pastebin и дать ссылку на нее здесь, чтобы мы могли увидеть, что вы изменили, чтобы заставить ее работать? - person DanC; 29.06.2010
comment
Я отредактировал сообщение. Не нужно привносить в это внешние зависимости, такие как pastebin. Я надеюсь, что это работает для вас, потому что я использовал JAXB, поскольку он распространяется с JDK. - person musiKk; 30.06.2010
comment
Спасибо за обновление вашего ответа. К сожалению, вы не указали аннотацию @XmlValue в поле RootElement. Когда я добавляю это обратно (поскольку мне нужно, чтобы значение поля стало содержимым элемента XML), я вижу исходное исключение, которое я вставил. Я полагаю, это означает, что либо я неправильно использую @XmlValue, либо есть ошибка с JAXB от Sun, каким-то образом связанная с @XmlValue. - person DanC; 04.07.2010
comment
Ну, если честно, я никогда не использовал @XmlValue, и после прочтения документации я действительно не понимаю, зачем он вам здесь нужен. Если это действительно необходимо, я не думаю, что смогу помочь, извините. - person musiKk; 04.07.2010