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