JAXB-5 动态 XML 生成

5 动态 XML 生成

JAXB 通过 JAXB.marshal 和 JAXB.unmarshal 接口,根据对象树的注解(Annotation),与 XML 文档相互转换;这类转换,通过 Java 类和注解(Annotation)的协调,完成 XML 的转换,我们称之为静态 XML 转换。更进一步地,通过 JAXBContext 相关接口,在 XML 转换过程中,进行 XML 结构性调整的方式,称之为动态 XML 转换。

5.1 实现 XML 动态 Element

使用 List<Object> + @XmlAnyElement 实现 XML 动态 Element
有时候,Products元素下面并不只是加入 product ,可能动态加入这种商品。 这次的模拟的商品是糕点与饼干。

这里的 Cake 只包含一个字段,需要注意的是 @XmlRootElement不能少。

@XmlRootElement(name = "Cake")
public class Cake {
    private String name;
    //setters, getters
}

这里的 Biscuit 也只包含一个字段。

@XmlRootElement(name = "Biscuit")
public class Biscuit {
    private String name;
    //setters, getters
}

Order的第三个字段是List,但是没有指定一个特定对象,用了Object代指所有,还有一个 @XmlAnyElement是最重要的注解,用来标注所有的Element,用于解组时 catch-all 捕获所有未能解组的元素。

@XmlRootElement(name = "Order")
@XmlAccessorType(XmlAccessType.FIELD)
public class Order4 {
    private String id;
    private Double price;
    @XmlElementWrapper(name = "Products")
    @XmlAnyElement
    private List<Object> product;
    //setters, getters
}

下面用来模拟数据生成,注意的是JAXBContext需要注册所有需要编组的Java bean。这一点和之前的例子是不同的。

@Test
public void test4() throws JAXBException {
    Cake cake = new Cake();
    cake.setName("Nobel");
    Biscuit biscuit = new Biscuit();
    biscuit.setName("PB");
    List<Object> list = Arrays.asList(cake, biscuit);
    Order4 order = new Order4();
    order.setId("1104");
    order.setProduct(list);
    JAXBContext context = JAXBContext.newInstance(Order4.class, Cake.class, Biscuit.class);
    Marshaller marshaller = context.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    marshaller.marshal(order, System.out);
}

生成的XML包含了一个 Products ,其数据结构在Order中并没有指定,方便后期扩展。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Order>
    <id>1104</id>
    <Products>
        <Cake>
            <name>Nobel</name>
        </Cake>
        <Biscuit>
            <name>PB</name>
        </Biscuit>
    </Products>
</Order>

5.2 实现 XML 动态 Attribute

使用 Map<QName, String> + @XmlAnyAttribute 实现 XML 动态 Attribute
与动态Element对应的是Attribute,不过需要注意的是,动态 Attribute 需要的是 Map ,而且其 key 的类型需要指定为 QName,这个QName在复杂的 XML 生成时有很大用处。一般使用到的是其QName(name),它还有一个形式为QName(name,namespace),可以指定命名空间,在某些场景下有不可替代的作用。
注意,需要为该动态 Element 标明 @XmlAnyAttribute 注解,用于 catch-all 所有未能解析的属性。

@XmlRootElement(name = "Order")
@XmlAccessorType(XmlAccessType.FIELD)
public class Order5 {
    @XmlAnyAttribute
    private Map<QName, String> properties;
    private Product product;
    //setters, getters
}
 @Test
 public void test5() throws JAXBException {
     Product p = new Product();
     p.setId("1100");
     p.setName("Apple");
     Map<QName, String> map = new HashMap<>();
     map.put(new QName("id"), "1105");
     map.put(new QName("classification"), "food");
     map.put(new QName("type"), "fruit");
     Order5 order = new Order5();
     order.setProduct(p);
     order.setProperties(map);
     JAXBContext context = JAXBContext.newInstance(Order5.class);
     Marshaller marshaller = context.createMarshaller();
     marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
     marshaller.marshal(order, System.out);
 }

仅关注 Order 的 Attribute , 可以发现我们在 Order 中没有指定任何与之相关的字段,只是在HashMap中加了几组数据,现在编组成为了Attribute。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Order id="1105" classification="food" type="fruit">
    <product id="1100">
        <name>Apple</name>
    </product>
</Order>

5.3 动态 XML 节点

使用 JAXBElement 封装对象,变更转换后的 XML 节点名称。

5.3.1 动态根节点

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

解决办法:使用 JAXBElement 类封装 Java Bean 类对象,实现与 @XmlRootElement 同样的效果;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);
}

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

得到的结果:

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

5.3.2 动态子节点

动态子节点
既然使用 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,需要使用 @XmlElementRef(name=“ref1”)关联使用到了 ObjectFactory 哪个方法,可以把@XmlElementRef(name=“ref1”)标注在对应的setter/getter方法上,或者标注在字段上,不过需要注意的是标注在字段上,还需要指定@XmlAccessorType(XmlAccessType.FIELD).
如果加在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>

5.3.3 利用继承关系

利用继承关系
已知:XML 中的节点元素都是对应着 Java 类的实例;
方案:可以利用继承关系,创建子类的实例,生成子类的 XML 节点元素,来动态生成 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 相关的设置都生效了。


上一章:JAXB-4 Java 类与 XML 的转换
目录:学习 JAXB
下一章:JAXB-6 XML 应用案例


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用JAXB生成XML时,如果需要动态生成`xmlns:xsi`属性,可以使用`javax.xml.bind.Marshaller.JAXB_SCHEMA_LOCATION`属性来指定XML Schema的位置和命名空间,并通过`javax.xml.bind.Marshaller.setProperty()`方法将该属性设置为要生成XML中的`xmlns:xsi`属性的值。例如: ``` JAXBContext jaxbContext = JAXBContext.newInstance(Root.class); Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "http://example.com/ns path/to/schema.xsd"); marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapper() { @Override public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) { if ("http://www.w3.org/2001/XMLSchema-instance".equals(namespaceUri)) { return "xsi"; } return null; } }); Root root = new Root(); marshaller.marshal(root, System.out); ``` 在这个例子中,`Marshaller.JAXB_SCHEMA_LOCATION`属性指定了XML Schema的位置和命名空间。`com.sun.xml.bind.namespacePrefixMapper`属性用于将`http://www.w3.org/2001/XMLSchema-instance`命名空间映射到`xsi`前缀。在生成XML时,JAXB会自动将`xmlns:xsi`属性添加到生成XML中,并将其值设置为`http://www.w3.org/2001/XMLSchema-instance`。例如: ``` <root xmlns="http://example.com/ns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://example.com/ns path/to/schema.xsd"> ... </root> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值