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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jiangchao858/article/details/82941043

摘要: 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 的场景,下一节还将关注于实用的场景。

猜你喜欢

转载自blog.csdn.net/jiangchao858/article/details/82941043