1. 程式人生 > >JAXB 深入顯出 - JAXB 教程 動態複雜XML生成

JAXB 深入顯出 - JAXB 教程 動態複雜XML生成

摘要: 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 的場景,下一節還將關注於實用的場景。