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

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

摘要: JAXB 作为JDK的一部分,能便捷地将Java对象与XML进行相互转换,本教程从实际案例出发来讲解JAXB 2 的那些事儿。完整版目录

前情回顾

上一节介绍的是关于Map转换方式,这一节开始,将基于Java Interface 接口做转换。
对于XML来说,接口是 Java 特有的数据形态,直接将Java的接口转化为XML结构是不可能的,但是可以通过间接的方式实现。

利用 @XmlRootElement

数据准备

现在,有一个’动物园’中包含很多动物。

  1. @XmlRootElement
  2. @XmlAccessorType(XmlAccessType.FIELD)
  3. public class Zoo {
  4. @XmlAnyElement
  5. public List<Animal> animals;
  6. // ignore setter/getter
  7. }

并不知道有什么动物,我们使用接口来定义 Animal。需要注意的是,这里需要使用注解@XmlAnyElement来标注接口。

  1. public interface Animal {
  2. void eat();
  3. void sleep();
  4. }

动物1号 ‘狗’ 出场

  1. @XmlRootElement
  2. public class Dog implements Animal{
  3. }

动物2号 ‘猫’ 出场

  1. @XmlRootElement
  2. public class Cat implements Animal{
  3. }

测试

  1. @Test
  2. public void test1() throws JAXBException {
  3. JAXBContext context = JAXBContext.newInstance(Zoo.class,Dog.class,Cat.class);
  4. Zoo zoo = new Zoo();
  5. zoo.setAnimals(Arrays.asList(new Dog(), new Cat()));
  6. Marshaller marshaller = context.createMarshaller();
  7. marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
  8. marshaller.marshal(zoo, System.out);
  9. }

生成的XML结果:

  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  2. <zoo>
  3. <dog/>
  4. <cat/>
  5. </zoo>

这里的 newInstance 中只指定了类不用指定接口。

  1. @Test
  2. public void test1_1() throws JAXBException {
  3. Zoo zoo = new Zoo();
  4. zoo.setAnimals(Arrays.asList(new Dog(), new Cat()));
  5. JAXB.marshal(zoo, System.out);
  6. }

这是使用静态方法实现的,之前的很多例子都是这样写的,但是对于复杂的类型,不能采用这种方式,需要显示指定所有需要处理的类给 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懵逼。

  1. @XmlAnyElement
  2. public Element[] others;
  1. @XmlAnyElement(lax="true")
  2. public Object[] others;
  1. @XmlAnyElement
  2. private List<Element> nodes;

采用 @XmlAnyElement 可以模糊化数据类型,相当于一种万能的注解,因为可以标注任意元素。但是,它也有诸多限制。

  • 只有元素不能被@XmlElement 或 @XmlElementRef 处理时,才交给@XmlAnyElement
  • @XmlAnyElement不能与XmlElement, XmlAttribute, XmlValue, XmlElements, XmlID, XmlIDREF合用
  • @XmlAnyElement在一个类中只能出现一次

利用 @XmlJavaTypeAdapter

采用适配器需要比较多的代码,但是在处理复杂的数据类型方面,它无所不能,之前在处理Map的时候,已经使用过,这里简单演示。

数据准备

食物有多种类型,其中一种是水果:

  1. @XmlRootElement
  2. @XmlAccessorType(XmlAccessType.FIELD)
  3. public class Food {
  4. public Fruit fruit1;
  5. public Fruit fruit2;
  6. // ignore setters/getters
  7. }

水果有多种形态,采用接口形式,简单起见,接口留空,不过使用注解@XmlJavaTypeAdapter来声明使用MyAdapter处理转化逻辑:

  1. @XmlJavaTypeAdapter(MyAdapter.class)
  2. public interface Fruit {
  3. }

西瓜是我最爱吃的水果之一,它有一个属性,标明其颜色:

  1. public class WaterMelon implements Fruit{
  2. public WaterMelon() {
  3. }
  4. public WaterMelon(String color) {
  5. this.color = color;
  6. }
  7. private String color;
  8. public String getColor() {
  9. return color;
  10. }
  11. public void setColor(String color) {
  12. this.color = color;
  13. }
  14. }

适配器很简单,直接返回对应的数据,不做任何特殊处理:

  1. public class MyAdapter extends XmlAdapter<WaterMelon, Fruit>{
  2. @Override
  3. public Fruit unmarshal(WaterMelon v) throws Exception {
  4. return v;
  5. }
  6. @Override
  7. public WaterMelon marshal(Fruit v) throws Exception {
  8. return (WaterMelon) v;
  9. }
  10. }

测试

  1. @Test
  2. public void test2() throws JAXBException {
  3. JAXBContext context = JAXBContext.newInstance(Food.class,WaterMelon.class);
  4. Food food = new Food();
  5. food.setFruit1(new WaterMelon("Green"));
  6. food.setFruit2(new WaterMelon("Red"));
  7. Marshaller marshaller = context.createMarshaller();
  8. marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
  9. marshaller.marshal(food, System.out);
  10. }

得到的XML数据:

  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  2. <food>
  3. <fruit1>
  4. <color>Green</color>
  5. </fruit1>
  6. <fruit2>
  7. <color>Red</color>
  8. </fruit2>
  9. </food>

这种方式有几个好处:

  • 万能的。掌握了这种方式可以处理多种JAXB不支持的复杂数据类型
  • 需要的注解比较少。只需要几个关键地方加注解
  • 更灵活。只需要修改Adapter就能达到多种输出结构

扩展Adapter

上面的例子中,使用到了接口,而Java中在介于类和接口之间还有一种形态——抽象类。

工人有多种角色,有的是雇主,有的是雇员:

  1. @XmlRootElement
  2. public class Worker {
  3. public Human employe;
  4. public Human employee;
  5. }

无论是管理者还是普通员工,都是由人扮演的:

  1. @XmlJavaTypeAdapter(ManAdapter.class)
  2. public interface Human {
  3. }

人类可以按照多种方式分别,性别是最简单的一种:

  1. public class Man implements Human{
  2. public String name;
  3. public int age;
  4. }
  1. public class Woman implements Human{
  2. public String name;
  3. public double salary;
  4. }

男人和女人都是Human,他们很多不同,但又诸多一样,于是将其抽象出来。

  1. public abstract class AbstractMan implements Human{
  2. public String name;
  3. }

这样,可以修改已经定义好的并且有重复属性的男人女人们:

  1. public class Woman extends AbstractMan{
  2. public int age;
  3. }

把他们之间不同的部分定义在自己的类中:

  1. public class Man extends AbstractMan{
  2. public double salary;
  3. }

适配器还和上面例子一样,直接返回:

  1. public class ManAdapter extends XmlAdapter<AbstractMan, Human>{
  2. @Override
  3. public Human unmarshal(AbstractMan v) throws Exception {
  4. return v;
  5. }
  6. @Override
  7. public AbstractMan marshal(Human v) throws Exception {
  8. return (AbstractMan) v;
  9. }
  10. }

测试

  1. @Test
  2. public void test3() throws JAXBException {
  3. JAXBContext context = JAXBContext.newInstance(Worker.class,Man.class,Woman.class);
  4. Worker worker = new Worker();
  5. Man man = new Man();
  6. man.name = "Zhangsan";
  7. worker.employe = man;
  8. Woman woman = new Woman();
  9. woman.age = 24;
  10. worker.employee = woman;
  11. Marshaller marshaller = context.createMarshaller();
  12. marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
  13. marshaller.marshal(worker, System.out);
  14. }

得到的XML数据:

  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  2. <worker>
  3. <employe xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="man">
  4. <name>Zhangsan</name>
  5. </employe>
  6. <employee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="woman">
  7. <age>24</age>
  8. </employee>
  9. </worker>

现在的Adapter还不够通用,因为没有什么逻辑,只返回自己的话,可以考虑Java中的Object这个‘万能’类型。

  1. public class AnyTypeAdapter extends XmlAdapter<Object, Object>{
  2. @Override
  3. public Object unmarshal(Object v) throws Exception {
  4. return v;
  5. }
  6. @Override
  7. public Object marshal(Object v) throws Exception {
  8. return v;
  9. }
  10. }

这样就可以处理任意的数据类型,传入的是Object,只要是Java对象,都能转换了。

为了验证我的说法,可以对代码稍作改动:

  1. @XmlJavaTypeAdapter(AnyTypeAdapter.class)
  2. public interface Human {
  3. }

这样就把转换工作交给了 AnyTypeAdapter,不需要改动其他代码,发现能得到相同的结果。

完整代码

可以在GitHub找到完整代码。
本节代码均在该包下:package com.example.demo.lesson15;

下节预览

本节介绍了 JAXB 中 Interface 的相关转化,内容比较多,也是Java转换XML的最后一节。下一节开始,将开始讲述XML转化为Java对象,也就是 Unmarshaller .

相关文章