【问题标题】:JAXB 2.x : How to override an XmlElement annotation from parent class - Mission Impossible?JAXB 2.x:如何覆盖父类的 XmlElement 注释 - 不可能的任务?
【发布时间】:2011-06-07 08:54:09
【问题描述】:

为什么这不可能?它看起来很简单,但它的行为并不像预期的那样。

总结:A 类使用聚合 DataA bean,而 B 类(A 类的子类)使用聚合 DataB bean(而 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 的功能,并通过使用 DataB bean 重用来自 DataA bean 的属性:

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 bean如下所示:

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 也会被编组,但它只是编写 bean DataA 的属性!为什么?在考虑 OOP 时,这并不是很直观。

当我也在 B 类上设置 @XmlElement 注释时...像这样:

@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 以产生预期的结果? 感谢您的任何反馈。

【问题讨论】:

  • 尝试将@XMLElement 放在字段而不是公共方法上
  • 没有帮助。我还尝试了在字段上没有任何 XmlElement 注释的 XmlAccessType.FIELD,我还尝试了在 GETTER 上没有任何 XmlElement 注释的 XmlAccessType.PROPERTY ...这是一个错误吗?像这样是不是真的没用。
  • ... 如您所见,我将注释 @XmlAccessorType(XmlAccessType.NONE) 放到两个类中...所以只有那些被 XmlElement 注释的元素才会被编组
  • 这已在 JRE 中继中修复:java.net/jira/browse/JAXB-804

标签: java inheritance jaxb marshalling jaxb2


【解决方案1】:

只是不要在类 B 上注释源属性。源属性已映射到父类,不应再次映射到子类。由于您正在注释 get/set 方法,因此将在 B 类上调用适当的 get/set。

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);
  }
}

更新

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 的文件,其中包含以下条目:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

【讨论】:

  • +1 很高兴见到你 :),1 问:在基类中它是私有的,那么它如何也进入子类?是因为重写了 setter getter 吗?
  • 太棒了,真的很有帮助!
  • @bzero - 来自 Oracle (Sun) 的 Java SE 6 将包含 JAXB 参考实现 Metro。我是 MOXy 技术主管。
  • 您可以将 MOXy 与以下任一配置一起使用:1) eclipselink.jar 2) 捆绑包:org.eclipse.persistence.asm、org.eclipse.persistence.core 和 org.eclipse.persistence .moxy.
  • @bzero - 就 JAXB 而言,该属性属于 DataA 类型,因为这是超类上的属性类型。当 DataB 的实例被编组时,xsi:type 属性用于将其限定为子类型的实例。签出:bdoughan.blogspot.com/2010/11/…
【解决方案2】:

非常感谢 Blaise 提供的所有这些提示。 MOXy 现在可以在我使用真正 bean 的真实应用程序中正常工作。不错!

我目前唯一的缺点是此配置代码中的最后一行不再起作用,因为 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";
    }
}

【讨论】:

  • 线路不适用于 MOXy:m.setProperty("com.sun.xml.internal.bind.namespacePrefixMapper", mapper);
  • MOXy 不支持 namespacePrefixMapper,但是您可以在 MOXy 中更轻松地做到这一点。 MOXy 将使用 @XmlSchema 注释中指定的前缀。示例见:stackoverflow.com/questions/3289644/…
  • 太棒了,谢谢!现在它完全可以正常工作了,因为它应该在 1 天前工作。
【解决方案3】:

你不需要使用 MOXy.. 只需更改 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>

【讨论】:

  • 嗨,这是一个有趣的方法,但你混淆了什么吗?因为有A类并且有“B扩展A”,所以A对B一无所知。在A类中应用实际上引用B类的注释正在破坏面向对象的数据建模......?
  • 也许您可以将@Also 仅应用于B 类?那么就可以了,我试试看。
【解决方案4】:

你可以使用这个方法:

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;
       }
}

这个映射对我有用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-14
    • 2021-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-28
    相关资源
    最近更新 更多