JAXB 深入显出 - JAXB 教程 动态复杂XML生成

x33g5p2x  于2021-12-28 转载在 其他  
字(7.0k)|赞(0)|评价(0)|浏览(387)

摘要: JAXB 作为JDK的一部分,能便捷地将Java对象与XML进行相互转换,本教程从实际案例出发来讲解JAXB 2 的那些事儿。完整版目录

前情回顾

前面介绍的都是基于最基本的编组过程。为了减少代码量,我接下来使用 JAXB 的静态方法演示编组过程。

@Test public void test1() throws JAXBException { Fruit fruit = new Fruit(); fruit.setColor("red"); JAXB.marshal(fruit, System.out); }

使用的 Fruit 只有一个字段,并且加了注解 @XmlRootElement(name = "水果")

@XmlRootElement(name = "水果") public class Fruit { private String color; // setters,getters }

得到的XML也很简单。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<水果>
    <color>red</color>
</水果>

动态根节点

已知:如果 @XmlRootElement不指定参数,则使用类名首字母小写作为根节点,如果指定name参数则使用其值作为根节点。
场景假设:XML的根节点需要根据业务场景变化,上例中的<水果>可以是任何传入的值,那么现有的方案无法实现这样的场景。

解决办法:需要使用到 JAXBElement,它可以代指任意 XML Element,并且在其初始化时,需要指定几个重要参数。

@Test public void test2() throws JAXBException { Fruit fruit = new Fruit(); fruit.setColor("red"); JAXBElement<Fruit> element = new JAXBElement<Fruit>(new QName("新鲜水果"), Fruit.class, fruit); JAXB.marshal(element, System.out); }

和上例的不同点在于编组的是 JAXBElement,而不直接作用于 Fruit,其第一个参数 QName就是指定根节点的名字,第二个参数指定需要编组的对象,第三个参数是真正的数据。要注意最后一行代码,传入的参数是 element

得到的结果:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<新鲜水果>
    <color>red</color>
</新鲜水果>

如果改一点代码:

@Test public void test2_2() throws JAXBException { GreenFruit fruit = new GreenFruit(); fruit.setColor("Green"); JAXBElement<GreenFruit> element = new JAXBElement<GreenFruit>(new QName("绿色水果"), GreenFruit.class, fruit); JAXB.marshal(element, System.out); }

得到的结果就是代码中设置的 QName。其实在 Fruit类上以已经包含注解@XmlRootElement(name = "水果"),这里设置的值直接覆盖之前注解的name

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<绿色水果>
    <color>Green</color>
</绿色水果>

可能你已经发现了,这里用到了GreenFruit而不是之前的Fruit,其实它们有相同的字段,只是 GreenFruit 直接没有加注解 @XmlRootElement,因为这个注解在这里所起的作用已经被 JAXBElement<>所替代了。

public class GreenFruit { private String color; // setters,getters }

动态子节点

既然使用 JAXBElement 可以动态指定参数值,如果某个Java 字段使用该类型是否可以做到动态生成XML子节点呢: Yes & No。

定义一个零食,第二个参数是 JAXBElement 的 水果,Fruit在之前一定定义过了。

@XmlRootElement public class Food { private String name; private JAXBElement<Fruit> element; // setters,getters }

这里还需要指定一个 ObjectFactory,ObjectFactory 类型的类里面可以定义一些创建某种类型的对象的方法,@XmlRegistry 用于标注在充当ObjectFactory角色的类上,@XmlElementDecl 声明对应的元素定义,其方法的返回值需要是JAXBElement类型,并且它必须指定一个name,这个name自由赋值,这里指定为’ref1’备用。

customElement 方法我直接返回null,因为实现细节不需要在这里写死,等下创建对象的时候再声明。

@XmlRegistry public class ObjectFactory { @XmlElementDecl(name = "ref1") public JAXBElement<Fruit> customElement(Fruit fruit){ return null; } }

Food中定义了 JAXBElement<Fruit>,需要使用 @XmlElementRef(name="ref1")关联使用到了 ObjectFactory 哪个方法,可以把@XmlElementRef(name="ref1")标注在对应的setter/getter方法上,或者标注在字段上,不过需要注意的是标注在字段上,还需要指定@XmlAccessorType(XmlAccessType.FIELD).

我习惯将注解标注在字段上,所以需要加@XmlAccessorType,如果加在get方法上就不需要加@XmlAccessorType.

@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Food { private String name; @XmlElementRef(name="ref1") private JAXBElement<Fruit> element; // setters,getters // @XmlElementRef(name="ref1") public JAXBElement<Fruit> getElement() { return element; } }

测试一下上面的写法是否正确。

@Test public void test4() throws JAXBException { Fruit fruit = new Fruit(); fruit.setColor("red"); JAXBElement<Fruit> element = new JAXBElement<Fruit>(new QName("时令水果"), Fruit.class, fruit); Food food = new Food(); food.setName("Some foods"); food.setElement(element); JAXBContext context = JAXBContext.newInstance(Fruit.class,Food.class); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(food, System.out); }

可以看到XML的子节点Fruit并不是之前指定的@XmlRootElement,而是测试代码中设置的值。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <food> <name>Some foods</name> <时令水果> <color>red</color> </时令水果> </food>

更改QName的值为‘生鲜水果’,发现生成的XML跟着变化。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<food>
    <name>Some foods</name>
    <生鲜水果>
        <color>red</color>
    </生鲜水果>
</food>

利用继承关系

既然 XML 中的节点元素都是对应着 Java 类,可以利用继承关系来动态生成 XML 元素。

‘商品信息’(Product.java)是之前用过的例子,它的第二个字段是引用类型:

@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Product { @XmlAttribute private String id; @XmlElementRef private Fruit fruit; // setters,getters }

‘水果’(Fruit.java)只有一个字段,并且已经设置了别名@XmlRootElement(name = "水果")

@XmlRootElement(name = "水果") public class Fruit { private String color; // setters,getters }

‘水果1’()继承了‘水果’,并且有一个特殊字段:

@XmlRootElement public class Pomelo extends Fruit{ private String name; // setters,getters }

‘水果2’()继承了‘水果’,并且有一个特殊字段:

@XmlRootElement public class Watermelon extends Fruit{ private String shape; // setters,getters }

当商品信息是第一种水果时:

@Test public void test5() throws JAXBException { Pomelo pomelo = new Pomelo(); pomelo.setName("柚子"); pomelo.setColor("Orange"); Product product = new Product(); product.setFruit(pomelo); product.setId("1205"); JAXBContext context = JAXBContext.newInstance(Product.class,Pomelo.class,Fruit.class); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(product, System.out); }

生成的 XML 如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product id="1205">
    <pomelo>
        <color>Orange</color>
        <name>柚子</name>
    </pomelo>
</product>

换一种水果再看看:

@Test public void test5_2() throws JAXBException { Watermelon watermelon = new Watermelon(); watermelon.setShape("椭圆形"); watermelon.setColor("Green"); Product product = new Product(); product.setFruit(watermelon); product.setId("1205"); JAXBContext context = JAXBContext.newInstance(Product.class,Watermelon.class); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(product, System.out); }

生成的 XML 如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product id="1205">
    <watermelon>
        <color>Green</color>
        <shape>椭圆形</shape>
    </watermelon>
</product>

商品信息每次根据不同的子商品而变化,之前已经设置过的主商品Fruit已经不能影响最终结果。

需要注意的是,这里不能直接使用静态工具类JAXB,下面的方式生成的结果不正确:

@Test public void test5_3() throws JAXBException { Watermelon watermelon = new Watermelon(); watermelon.setShape("椭圆形"); watermelon.setColor("Green"); Product product = new Product(); product.setFruit(watermelon); product.setId("1205"); JAXB.marshal(product, System.out); }

得到的 XML 和之前的预期不一致:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product id="1205">
    <水果>
        <color>Green</color>
    </水果>
</product>

因为 JAXB 工具类在注册newInstance时,只关注第一个参数JAXB.marshal(object, out),而这里的第一个参数是Product,因此不能注册Fruit的子类 Watermelon,所有与 Watermelon 相关的设置都不能成功,不过这里与父类 Fruit 相关的设置都生效了。

完整代码

可以在GitHub找到完整代码。
本节代码均在该包下:package com.example.demo.lesson11;

下节预览

本节介绍了 JAXB 编组为复杂 XML 的场景,下一节还将关注于实用的场景。

相关文章