1. 程式人生 > >JAXB 深入顯出 - JAXB 教程 Interface 介面轉化為XML

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 .