XmlID / XmlIDREF и наследование в последней версии JAXB (Java 7) - PullRequest
5 голосов
/ 22 ноября 2011

Я использую теги @XmlID и @XmlIDREF для ссылки на один объект из другого. В Java 6 он работал нормально даже с унаследованными классами. Пример кода, который я создал, выглядит следующим образом. Базовый класс используемых тегов:

@XmlRootElement
@XmlAccessorType(FIELD)
public class Module {
    Module() {}

    @XmlIDREF
    private Module other;

    @XmlID
    private String id;

    public Module(String id, Module other) {
        this.id = id;
        this.other = other;
    }
}

Унаследованный класс:

@XmlRootElement
public class TheModule extends Module {
    TheModule() {}

    private String feature;

    public TheModule(String id, Module other, String feature) {
        super(id, other);
        this.feature = feature;
    }
}

Контейнер для этих классов:

@XmlRootElement
public class Script {
    Script() {}
    public Script(Collection<Module> modules) {
        this.modules = modules;
    }

    @XmlElementWrapper
    @XmlElementRef
    Collection<Module> modules = new ArrayList<Module>();
}

При выполнении этого примера кода:

public class JaxbTest {

    private Script createScript() {
        Module m1 = new Module("Module1", null);
        Module m2 = new TheModule("Module2", m1, "featured  module");
        Module m3 = new Module("Module3", m2);
        return new Script(Arrays.asList(m1, m2, m3));
    }

    private String marshal(Script script) throws Exception {
        JAXBContext context = JAXBContext.newInstance(Module.class, Script.class, TheModule.class);
        Writer writer = new StringWriter();
        context.createMarshaller().marshal(script, writer);
        return writer.toString();
    }

    private void runTest() throws Exception {
        Script script = createScript();

        System.out.println(marshal(script));
    }

    public static void main(String[] args) throws Exception  {
        new JaxbTest().runTest();
    }
}

Я получаю XML, в Java 6:

<script>
  <modules>
    <module>
      <id>Module1</id>
    </module>
    <theModule>
      <other>Module1</other>
      <id>Module2</id>
      <feature>featured module</feature>
    </theModule>
    <module>
      <other>Module2</other>
      <id>Module3</id>
    </module>
  </modules>
</script>

Обратите внимание, что ссылка на m2 (экземпляр TheModule) сериализуется, как и ожидалось. Но когда тот же код работает под Java 7 (Jaxb 2.2.4-1), я получаю:

<script>
  <modules>
    <module>
      <id>Module1</id>
    </module>
    <theModule>
      <other>Module1</other>
      <id>Module2</id>
      <feature>featured module</feature>
    </theModule>
    <module>
      <other xsi:type="theModule" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <other>Module1</other>
        <id>Module2</id>
        <feature>featured module</feature>
      </other>
      <id>Module3</id>
    </module>
  </modules>
</script>

Итак, вы можете видеть, что на последнем JAXB @XmlIDREF для унаследованного модуля не работает!

Ответы [ 3 ]

2 голосов
/ 02 января 2012

Этот ответ неверен . Вместо того, чтобы полагаться на него, прочитайте документацию для JAXBContext.newInstance(...), см. этот ответ и комментарии ниже.


Я думаю, вы путаете JAXB со следующей строкой.

JAXBContext.newInstance(Module.class, Script.class, TheModule.class);

Вы говорите, что хотите сериализовать типы XML Script, Module и TheModule. JAXB будет обрабатывать объекты этого последнего типа специальным способом, потому что вы уже предоставили его базовый класс: он добавляет к нему различающий атрибут. Таким образом, эти два типа могут различаться в сериализованном XML.

Попробуйте указать только Script и Module, базовый класс для всех модулей.

JAXBContext.newInstance(Module.class, Script.class);

На самом деле, вы можете полностью исключить Module. JAXB будет выводить типы в Script объекте, который вы пытаетесь сериализовать из контекста.

Кстати, это поведение не точно , связанное с Java 6. Это связано с используемой реализацией JAXB (хорошо-хорошо, почти то же самое, я знаю). В своих проектах я использую JAXB 2.2.4-1, который воспроизводит проблему в Java 6 и 7.

Да, и еще одна вещь: вместо создания StringWriter и упорядочения объектов в нем, а затем отправки его содержимого на System.out вы можете использовать следующую команду для отправки отформатированного XML на stdout.

Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(script, System.out);

Может быть, это может немного облегчить дальнейшее тестирование.

0 голосов
/ 19 июня 2017

Если вы все еще застряли с Java7 в 2017 году, то есть простой способ обойти это.Проблема в том, что при сериализации с XmlREFId Jaxb не может справиться с подклассами.Решением является использование адаптера и возврат нового экземпляра базового класса с тем же идентификатором, что и сериализуемый (будет использоваться только идентификатор, поэтому нам не нужно беспокоиться о других полях):

public class ModuleXmlAdapter extends XmlAdapter<Module, Module> {
    @Override
    public Module unmarshal(Module v) {
        // there is no problem with deserializing - return as is
        return v;
    }

    @Override
    public Module marshal(Module v) {
        if (v == null) {
            return null;
        }
        // here is the trick:
        return new Module(v.getId(), null);
    }
}

Затем просто используйте этот адаптер там, где это необходимо:

public class Module {
    @XmlIDREF
    @XmlJavaTypeAdapter(ModuleXmlAdapter.class)
    private Module other;

    //...
}
0 голосов
/ 23 июля 2012

Кажется, это известная ошибка в JAXB.У меня была та же проблема, и я решил ее, не используя XmlIDRef, а используя postDeserialize с пользовательским идентификатором.

Вот отчет об ошибке:

http://java.net/jira/browse/JAXB-870

Я не смог воспользоваться предложением Кохани РобертаЭто сработало для вас?

...