JAXB 2.x: Как переопределить аннотацию XmlElement из родительского класса - Миссия невыполнима? - PullRequest
8 голосов
/ 11 января 2011

Почему это невозможно? Кажется, все так просто, но не работает так, как ожидалось.

Резюме: Класс A использует агрегированный компонент DataA, тогда как класс B (подкласс класса A) использует агрегированный компонент DataB (тогда как DataB расширяет DataA).

Я написал эти тестовые классы для визуализации и объяснения моего вопроса:

Класс A:

package test;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name="root")
public class A {

  private DataA source = new DataA();

  @XmlElement(name="source")
  public DataA getSource() {
    return source;
  }

  public void setSource(DataA source) {
    this.source = source;
  }

}

и его класс DataA (я использовал аннотацию FIELD, чтобы все поля были распределены):

package test;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;

@XmlAccessorType(XmlAccessType.FIELD)
public class DataA {

    public String string1 = "1";
    public String string2 = "2";

}

А теперь класс B (подкласс класса A): Моя цель состоит в том, чтобы повторно использовать функциональные возможности A, а также повторно использовать свойства из компонента DataA с помощью компонента DataB:

package test;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name="root")
public class B extends A {

  private DataB source = new DataB();

  public DataB getSource() {
    return this.source;
  }

  public void setSource(DataB source) {
    this.source = source;
  }

}

Соответствующий ему компонент DataB выглядит следующим образом:

package test;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;

@XmlAccessorType(XmlAccessType.FIELD)
public class DataB extends DataA {
    public String string3 = "3";
}

Теперь, когда я собираю экземпляр класса A, он выдает следующее:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
  <source>
    <string1>1</string1>
    <string2>2</string2>
  </source>
</root>

Когда я собираю экземпляр класса B, я получаю тот же результат:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
  <source>
    <string1>1</string1>
    <string2>2</string2>
  </source>
</root>

Но я ожидал, что string3 тоже будет маршализованным, но он только записывает свойства компонента DataA! ЗАЧЕМ? Это не совсем интуитивно, если думать с точки зрения ООП.

Когда я устанавливаю аннотацию @XmlElement также для класса B ... вот так:

@XmlElement
public DataB getSource() {
    return this.source;
}

... тогда свойство дважды упорядочивается, поскольку оно аннотируется как родительским классом, так и дочерним классом. Это также то, что я не хочу:

Теперь вывод:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <source xsi:type="dataB" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <string1>1</string1>
        <string2>2</string2>
        <string3>3</string3>
    </source>
    <source>
        <string1>1</string1>
        <string2>2</string2>
        <string3>3</string3>
    </source>
</root>

В результате я ожидал от JAXB следующего XML:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <source>
        <string1>1</string1>
        <string2>2</string2>
        <string3>3</string3>
    </source>
</root>

Есть какие-нибудь подсказки, как настроить JAXB для получения ожидаемого результата? Спасибо за любой отзыв.

Ответы [ 4 ]

3 голосов
/ 11 января 2011

Только не аннотируйте свойство источника в классе B. Свойство источника было сопоставлено с родительским классом и не должно отображаться снова в дочернем классе. Поскольку вы аннотируете методы get / set, соответствующий класс get / set будет вызываться для класса B.

package test;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name="root")
public class B extends A {

  private StringBuffer source = null;

  public String getSource() {
    return source.toString();
  }

  public void setSource(String source) {
    this.source = new StringBuffer(source);
  }
}

UPDATE

Может быть ошибка в Metro JAXB (эталонная реализация). Когда я запускаю этот обновленный пример с EclipseLink JAXB (MOXy) , я получаю следующий вывод:

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <source>
      <string1>1</string1>
      <string2>2</string2>
   </source>
</root>
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="b">
   <source xsi:type="dataB">
      <string1>1</string1>
      <string2>2</string2>
      <string3>3</string3>
   </source>
</root>

Это можно воспроизвести с помощью следующего кода:

package test;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(A.class, B.class);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        A a = new A();
        DataA da = new DataA();
        da.string1 = "1";
        da.string2 = "2";
        a.setSource(da);
        marshaller.marshal(a, System.out);

        B b = new B();
        DataB db = new DataB();
        db.string1 = "1";
        db.string2 = "2";
        db.string3 = "3";
        b.setSource(db);
        marshaller.marshal(b, System.out);
    }
}

Чтобы использовать MOXy в качестве реализации JAXB, вам нужно предоставить файл с именем jaxb.properties в пакете модели (test) со следующей записью:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
1 голос
/ 10 февраля 2011

вам не нужно использовать МОКСИ .. Просто измените класс B и используйте @ XmlAlso.

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "root")
public class B extends A {
}


@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso({ DataA.class, DataB.class })
@XmlRootElement(name = "source")
@XmlType(name = "source")

public class DataA {
   private String string1 = "1";
   private String string2 = "2";
.....//getters and setters here
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "source")
public class DataB extends DataA {
    private String string3 = "3";
.....//getters and setters here
}

@XmlAccessorType(XmlAccessType.FIELD) 
@XmlSeeAlso({ A.class, B.class }) 
@XmlRootElement(name = "root") 

public class A {

    private DataA source = new DataA();

    public DataA getSource() {
        return source;
    }

    public void setSource(DataA source) {
        this.source = source;
    }

}


 B b = new B();
        DataB db = new DataB();
        db.setString1("1");
        db.setString2("2");
        db.setString3("3");
        b.setSource(db);
        marshaller.marshal(b, System.out);

НАКОНЕЧНО ЗАПИСИТ:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <source>
        <string1>1</string1>
        <string2>2</string2>
    </source>
</root>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <source xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="dataB">
        <string1>1</string1>
        <string2>2</string2>
        <string3>3</string3>
    </source>
</root>
0 голосов
/ 21 марта 2013

Вы можете использовать этот метод:

public interface Data {}

@XmlTransient
public abstract class A {
       private Data data;

       public Data getData(){
              return data;
       }

       public void setData(Data data){
              this.data = data;
       }
}

@XmlAccessorType(XmlAccessType.NONE)
public class ClassA extends A {

       // DataA implements Data
       @XmlElement(type=DataA.class)
       public Data getData(){
              return super.getData();
       }

       public void setData(DataA data){
              super.setData(data);
       }
}

@XmlAccessorType(XmlAccessType.NONE)
public class ClassB extends A {
       // DataB implements Data
       private DataB data;

       @XmlElement(type=DataB.class)
       public DataA getData(){
              return data;
       }

       public void setData(DataB data){
              this.data = data;
       }
}

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement
public class ClassC {
       @XmlElement
       private ClassA classA;
       @XmlElement
       private ClassB classB;

       public ClassA getClassA() {
              return classA;
       }

       public void setClassA(ClassA classA) {
              this.classA = classA;
       }

       public ClassB getClassB() {
              return classB;
       }

       public ClassB setClassB(ClassB classB) {
              this.classB = classB;
       }
}

это отображение сработало для меня.

0 голосов
/ 11 января 2011

Большое спасибо, Блейз, за ​​все эти советы.MOXy теперь отлично работает с моим реальным приложением с настоящими бобами.nice!

Единственный недостаток, который у меня сейчас есть, заключается в том, что последняя строка в этом коде конфигурации больше не работает, потому что MOXy использует, конечно, другой механизм преобразования префиксов пространства имен.

У вас есть указатель дляэтот?Я искал документацию MOXy и искал пространство имен, но ничего похожего не было найдено.

    NamespacePrefixMapper mapper = new PreferredMapper();
    Marshaller m = context.createMarshaller();
    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
    m.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
    m.setProperty("com.sun.xml.internal.bind.namespacePrefixMapper", mapper);

    public static class PreferredMapper extends NamespacePrefixMapper {
      @Override
      public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
        return "z";
    }
}
...