JAXB 深入顯出 - JAXB 教程 Interface 介面轉化為XML
摘要: JAXB 作為JDK的一部分,能便捷地將Java物件與XML進行相互轉換,本教程從實際案例出發來講解JAXB 2 的那些事兒。完整版目錄
前情回顧
上一節介紹的是關於Map轉換方式,這一節開始,將基於Java Interface 介面做轉換。
對於XML來說,介面是 Java 特有的資料形態,直接將Java的介面轉化為XML結構是不可能的,但是可以通過間接的方式實現。
利用 @XmlRootElement
資料準備
現在,有一個’動物園’中包含很多動物。
@XmlRootElement
@XmlAccessorType(XmlAccessType. FIELD)
public class Zoo {
@XmlAnyElement
public List<Animal> animals;
// ignore setter/getter
}
並不知道有什麼動物,我們使用介面來定義 Animal
。需要注意的是,這裡需要使用註解@XmlAnyElement
來標註介面。
public interface Animal {
void eat();
void sleep();
}
動物1號 ‘狗’ 出場
@XmlRootElement
public class Dog implements Animal{
}
動物2號 ‘貓’ 出場
@XmlRootElement
public class Cat implements Animal{
}
測試
@Test
public void test1() throws JAXBException {
JAXBContext context = JAXBContext.newInstance(Zoo.class,Dog.class,Cat.class);
Zoo zoo = new Zoo();
zoo.setAnimals(Arrays.asList(new Dog(), new Cat()));
Marshaller marshaller = context. createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(zoo, System.out);
}
生成的XML結果:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<zoo>
<dog/>
<cat/>
</zoo>
這裡的 newInstance
中只指定了類不用指定介面。
@Test
public void test1_1() throws JAXBException {
Zoo zoo = new Zoo();
zoo.setAnimals(Arrays.asList(new Dog(), new Cat()));
JAXB.marshal(zoo, System.out);
}
這是使用靜態方法實現的,之前的很多例子都是這樣寫的,但是對於複雜的型別,不能採用這種方式,需要顯示指定所有需要處理的類給 JAXBContext.newInstance
XXX是一個介面而JAXB不能處理介面
在處理介面過程中,有時候可能會看到如下異常資訊:
com.sc.md.datatypes.schemas.csemessage.EnvelopeType is an interface, and JAXB can't handle interfaces.
或者這樣 com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions com.example.demo.lesson15.Animal是介面, 而 JAXB 無法處理介面。 al.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
錯誤分析:
在上面的介面集合上,加了一個註解@XmlAnyElement
,該註解起著至關重要的作用,告訴JAXB這是一個不確定型別的屬性。
擴充套件 @XmlAnyElement
之前很少使用到 @XmlAnyElement,因為一般的Java屬性都明確知道型別,但是下面幾種寫法可能使JAXB懵逼。
@XmlAnyElement
public Element[] others;
@XmlAnyElement(lax="true")
public Object[] others;
@XmlAnyElement
private List<Element> nodes;
採用 @XmlAnyElement
可以模糊化資料型別,相當於一種萬能的註解,因為可以標註任意元素。但是,它也有諸多限制。
- 只有元素不能被@XmlElement 或 @XmlElementRef 處理時,才交給@XmlAnyElement
- @XmlAnyElement不能與XmlElement, XmlAttribute, XmlValue, XmlElements, XmlID, XmlIDREF合用
- @XmlAnyElement在一個類中只能出現一次
利用 @XmlJavaTypeAdapter
採用介面卡需要比較多的程式碼,但是在處理複雜的資料型別方面,它無所不能,之前在處理Map的時候,已經使用過,這裡簡單演示。
資料準備
食物有多種型別,其中一種是水果:
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Food {
public Fruit fruit1;
public Fruit fruit2;
// ignore setters/getters
}
水果有多種形態,採用介面形式,簡單起見,介面留空,不過使用註解@XmlJavaTypeAdapter
來宣告使用MyAdapter
處理轉化邏輯:
@XmlJavaTypeAdapter(MyAdapter.class)
public interface Fruit {
}
西瓜是我最愛吃的水果之一,它有一個屬性,標明其顏色:
public class WaterMelon implements Fruit{
public WaterMelon() {
}
public WaterMelon(String color) {
this.color = color;
}
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
介面卡很簡單,直接返回對應的資料,不做任何特殊處理:
public class MyAdapter extends XmlAdapter<WaterMelon, Fruit>{
@Override
public Fruit unmarshal(WaterMelon v) throws Exception {
return v;
}
@Override
public WaterMelon marshal(Fruit v) throws Exception {
return (WaterMelon) v;
}
}
測試
@Test
public void test2() throws JAXBException {
JAXBContext context = JAXBContext.newInstance(Food.class,WaterMelon.class);
Food food = new Food();
food.setFruit1(new WaterMelon("Green"));
food.setFruit2(new WaterMelon("Red"));
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(food, System.out);
}
得到的XML資料:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<food>
<fruit1>
<color>Green</color>
</fruit1>
<fruit2>
<color>Red</color>
</fruit2>
</food>
這種方式有幾個好處:
- 萬能的。掌握了這種方式可以處理多種JAXB不支援的複雜資料型別
- 需要的註解比較少。只需要幾個關鍵地方加註解
- 更靈活。只需要修改Adapter就能達到多種輸出結構
擴充套件Adapter
上面的例子中,使用到了介面,而Java中在介於類和介面之間還有一種形態——抽象類。
工人有多種角色,有的是僱主,有的是僱員:
@XmlRootElement
public class Worker {
public Human employe;
public Human employee;
}
無論是管理者還是普通員工,都是由人扮演的:
@XmlJavaTypeAdapter(ManAdapter.class)
public interface Human {
}
人類可以按照多種方式分別,性別是最簡單的一種:
public class Man implements Human{
public String name;
public int age;
}
public class Woman implements Human{
public String name;
public double salary;
}
男人和女人都是Human,他們很多不同,但又諸多一樣,於是將其抽象出來。
public abstract class AbstractMan implements Human{
public String name;
}
這樣,可以修改已經定義好的並且有重複屬性的男人女人們:
public class Woman extends AbstractMan{
public int age;
}
把他們之間不同的部分定義在自己的類中:
public class Man extends AbstractMan{
public double salary;
}
介面卡還和上面例子一樣,直接返回:
public class ManAdapter extends XmlAdapter<AbstractMan, Human>{
@Override
public Human unmarshal(AbstractMan v) throws Exception {
return v;
}
@Override
public AbstractMan marshal(Human v) throws Exception {
return (AbstractMan) v;
}
}
測試
@Test
public void test3() throws JAXBException {
JAXBContext context = JAXBContext.newInstance(Worker.class,Man.class,Woman.class);
Worker worker = new Worker();
Man man = new Man();
man.name = "Zhangsan";
worker.employe = man;
Woman woman = new Woman();
woman.age = 24;
worker.employee = woman;
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(worker, System.out);
}
得到的XML資料:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worker>
<employe xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="man">
<name>Zhangsan</name>
</employe>
<employee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="woman">
<age>24</age>
</employee>
</worker>
現在的Adapter還不夠通用,因為沒有什麼邏輯,只返回自己的話,可以考慮Java中的Object這個‘萬能’型別。
public class AnyTypeAdapter extends XmlAdapter<Object, Object>{
@Override
public Object unmarshal(Object v) throws Exception {
return v;
}
@Override
public Object marshal(Object v) throws Exception {
return v;
}
}
這樣就可以處理任意的資料型別,傳入的是Object,只要是Java物件,都能轉換了。
為了驗證我的說法,可以對程式碼稍作改動:
@XmlJavaTypeAdapter(AnyTypeAdapter.class)
public interface Human {
}
這樣就把轉換工作交給了 AnyTypeAdapter
,不需要改動其他程式碼,發現能得到相同的結果。
完整程式碼
可以在GitHub找到完整程式碼。
本節程式碼均在該包下:package com.example.demo.lesson15;
下節預覽
本節介紹了 JAXB 中 Interface 的相關轉化,內容比較多,也是Java轉換XML的最後一節。下一節開始,將開始講述XML轉化為Java物件,也就是 Unmarshaller .