JAXB 深入显出 - JAXB 教程 Interface 接口转化为XML

x33g5p2x  于2021-12-28 转载在 其他  
字(6.0k)|赞(0)|评价(0)|浏览(264)

摘要: 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 .

相关文章