JAXB纯粹从接口编组

Tre*_*son 13 java jax-rs jaxb

我有一个复杂的Java接口层次结构,我想用JAXB编组(而不一定是unmarshal).这些接口表示将从JAX-RS REST API返回的对象,如XML,JSON,YAML等.(我正在使用RestEasy,它可以用XML以外的格式封送JAXB注释类型.)

问题似乎是JAXB基本上是面向类的.我已经对JAXB和接口的困难进行了大量的网络研究,最接近的解决方案是MOXy JAXB - 映射接口到XMLJAXB以及接口前端模型.但是,我有两个主要问题:a)我想在接口方面注释/工作,而不是具体类(其中将有多个实现并包含不应该被编组的重要其他状态),以及b)我有多级接口继承.这是接口的示例,减去目前为止的任何JAXB注释:

interface Uuided {
  UUID getId();
}
interface Named {
  String getName();
}
interface Component extends Uuided, Named {
  Map<String, ComponentAttribute> getAttributes();
}
interface Attribute extends Named {
  Type getType();
  Object getValue();
}
interface ComponentAttribute extends Attribute {
  Component getDeclaringComponent();
}
Run Code Online (Sandbox Code Playgroud)

理想情况下,这会产生类似于:

<component id="xxx" name="thing">
  <attributes>
    <componentAttribute name="color">
      <type><stringType/></type>
      <value>green</value>
      <declaringComponent idref="xxx"/>
    </componentAttribute>
  </attributes>
</component>
Run Code Online (Sandbox Code Playgroud)

显然,在摘要中,这导致了诸如确定最衍生的带注释的接口之类的问题,理论上可以有多个接口.但是,在我的情况下,我认为具体类只实现了一个应该被封送的单个接口.解组不是必需的,因为我有单独的类定义upsert属性.

所以我的问题是,这甚至可以用JAXB来处理,如果是这样,怎么样?即使我必须非常明确地定义绑定,适配器等,我也希望在JAXB框架内工作以获得RestEasy中所有非XML提供程序的好处.

ale*_*ncu 6

答案很简单:使用@XmlElement(type = Object.class)你的界面领域。

详情如下:

我发现了两种使JAXB序列化接口的方法:

  1. @XmlAnyElement
  2. @XmlElement(type = Object.class)

1. @ XmlAnyElement

只需使用注释您的接口类型字段,@XmlAnyElementJAXB就会根据其具体类型对接口进行序列化。不要忘记用注释具体类型@XmlRootElement并将具体类型添加到JAXBContext中。完整示例如下:

public class InterfaceSerializer {

    @XmlRootElement
    public static class Pojo {
        Pojo() {
            field1 = new PojoFieldImpl1();
            field2 = new PojoFieldImpl2();
            field3 = new PojoFieldImpl1();
        }

        @XmlAnyElement
        public IPojoField field1;
        @XmlAnyElement
        public IPojoField field2;
        @XmlAnyElement
        public IPojoField field3;

        @Override
        public String toString() {
            return "field1 = " + field1 + "\nfield2 = " + field2 + "\nfield3 = " + field3;
        }
    }

    public static interface IPojoField {

    }

    @XmlRootElement
    public static class PojoFieldImpl1 implements IPojoField {

        PojoFieldImpl1() {
            value = "PojoFieldImpl1 value";
        }

        public String value;

        @Override
        public String toString() {
            return value;
        }
    }

    @XmlRootElement
    public static class PojoFieldImpl2  implements IPojoField {

        PojoFieldImpl2() {
            value = "PojoFieldImpl2 value1";
            value2 = "PojoFieldImpl2 value2";
        }

        public String value;
        public String value2;

        @Override
        public String toString() {
            return value + " " + value2;
        }
    }

    public static void main(String []args) throws JAXBException {
        Pojo pojo = new Pojo();
        JAXBContext jaxbContext = JAXBContext.newInstance(Pojo.class, PojoFieldImpl1.class, PojoFieldImpl2.class);
        Marshaller marshaller = jaxbContext.createMarshaller();

        marshaller.marshal(pojo, new File("interfaceSerializer.xml"));
    }
}
Run Code Online (Sandbox Code Playgroud)

输出XML:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pojo>
    <pojoFieldImpl1>
        <value>PojoFieldImpl1 value</value>
    </pojoFieldImpl1>
    <pojoFieldImpl2>
        <value>PojoFieldImpl2 value1</value>
        <value2>PojoFieldImpl2 value2</value2>
    </pojoFieldImpl2>
    <pojoFieldImpl1>
        <value>PojoFieldImpl1 value</value>
    </pojoFieldImpl1>
</pojo>
Run Code Online (Sandbox Code Playgroud)

这种方法的缺点:

  • 您无法将XML与pojo中的每个字段区分开(相同的实现将使用相同的标记编写)
  • 您没有任何类型信息可以解组 XML(如果您愿意的话)

第二个解决方案中解决了这些缺点:

2. @ XmlElement(类型= Object.class)

我在mikesir87的博客文章中偶然发现了这一点。只需将@XmlAnyElement上方的注释替换为上方@XmlElement(type = Object.class) 的Pojo类中应具有以下内容:

@XmlElement(type = Object.class)
public IPojoField field1;
@XmlElement(type = Object.class)
public IPojoField field2;
@XmlElement(type = Object.class)
public IPojoField field3;
Run Code Online (Sandbox Code Playgroud)

重新运行我们的示例,得到的XML:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pojo>
    <field1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pojoFieldImpl1">
        <value>PojoFieldImpl1 value</value>
    </field1>
    <field2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pojoFieldImpl2">
        <value>PojoFieldImpl2 value1</value>
        <value2>PojoFieldImpl2 value2</value2>
    </field2>
    <field3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pojoFieldImpl1">
        <value>PojoFieldImpl1 value</value>
    </field3>
</pojo>
Run Code Online (Sandbox Code Playgroud)

也可以反序列化:

Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Pojo unmarshalledPojo = (Pojo) unmarshaller.unmarshal(new File("interfaceSerializer.xml"));  
System.out.println(unmarshalledPojo);
Run Code Online (Sandbox Code Playgroud)

结果输出:

field1 = PojoFieldImpl1 value
field2 = PojoFieldImpl2 value1 PojoFieldImpl2 value2
field3 = PojoFieldImpl1 value
Run Code Online (Sandbox Code Playgroud)

可能是一个“ 骇人听闻的 ”解决方案,但可以完成工作。


Tre*_*son 1

我认为答案是 JAXB 根​​本无意支持这一点,并且试图强制它是愚蠢的。此外,JAXB 驱动的 JSON 封送处理也不是理想的选择。

我最终编写了自己的编组框架,并带有自己的一组注释:

@MarshalMixin // marshal fields but not a top-level object
interface Uuided {
  @MarshalAsString // ignore properties; just call toString()
  @MarshalId // treat as identifier for @MarshalUsingIds or cyclic ref
  UUID getId();
}
@MarshalMixin
interface Named {
  @MarshalId
  String getName();
}
@MarshalObject // top-level marshaled object providing class name
interface Component extends Uuided, Named {
  @MarshalAsKeyedObjectMap(key = "name") // see description below
  Map<String, ComponentAttribute> getAttributes();
}
@MarshalObject
interface Attribute extends Named {
  Type getType();
  @MarshalDynamic // use run-time (not declared) type
  Object getValue();
}
interface ComponentAttribute extends Attribute {
  @MarshalUsingIds
  Component getDeclaringComponent();
}
Run Code Online (Sandbox Code Playgroud)

生成的封送拆收器写入抽象层(当前针对 JSON 和 XML 实现)。这提供了更大的灵活性,可以使输出自然地适应不同的表示,而无需大量注释和适配器。例如,我所说的键控对象映射,其中每个对象都包含其映射键。在 JSON 中,您需要一个映射,但在 XML 中,您需要一个序列:

{..., map: {'a': {'name': 'a', ...}, 'b': {'name: 'b', ...}, ...}, ...}
...<map><thing name='a'>...</thing><thing name='b'>...</thing></map>...
Run Code Online (Sandbox Code Playgroud)

似乎还有多达 4 个人也关心这个问题,所以希望我最终能开源它。:-)

  • 为什么编组接口是愚蠢的?这是完全有效的要求,但它不允许 JAXB 规范失败。现在我必须为每个接口创建一个哑类。 (5认同)