JAXB学习(二): 对JAXB支持的主要注解的说明
我们在上一篇中对JAXB有了一个大致的认识,现在我们来了解JAXB的一些主要注解。
顶层元素:XmlRootElement
表示整个XML文档的类应该使用XmlRootElement修饰,其实就像之前那个简单例子那样,XmlRootElement也是最简单JAXB应用唯一需要用到的注解。
控制元素的选择:XmlAccessorType XmlTransient
如果JAXB绑定一个java类为XML,那么默认的会绑定所有public成员,包括 public的getter和setter对(必须同时有getter和setter)或者是public的属性。任何protected ,default和private的成员只有在被一个恰当的注解(例如 XmlElement 或者XmlAttribute)修饰时才会被绑定。 我们有几种方式来影响这种默认的行为。
1. 在 包 或者 顶层元素(也就是XmlRootElement修饰的类)上 使用 XmlAccessorType, 它的值有 FIELD, PROPERTY, PUBLIC_MEMBER or NONE
FIELD : 任何非static 非 transient 的属性将会被绑定
PROPERTY :任何getter和setter对
PUBLIC_MEMBER : 这个就是上面描述的默认情况
NONE : NONE会压制任何绑定,除非明确的使用XmlElement或XmlAttribute修饰。
没有这个注解的类 可以从父类或者包级别的配置来继承。
2. 使用XmlTransient, 它会压制它的目标绑定。 考虑下面这种情况,有一个public 的属性foo,还有一对getFoo和setFoo,如果我们使用默认的配置将会出现 命名冲突,这时就可以使用XmlTransient来压制其中一个。
下面的例子我们在包级别使用XmlAccessorType,将绑定设置为 FIELD, 然后使用XmlTransient 压制其中的一个public属性。
首先在包下面建一个package-info.java 文件用来进行包注释。
@javax.xml.bind.annotation.XmlAccessorType(javax.xml.bind.annotation.XmlAccessType.FIELD)
package com.massclouds.test;
顶层元素类:
package com.massclouds.test; import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient; @XmlRootElement
public class Person {
public String name;
@XmlTransient
public int age; private String gender; public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
在上面的配置中,Person的name和gender属性可以被绑定到xml中。 注意gender之所以会被绑定并不是因为getter和setter对,而是应为FIELD级别会将private的属性也绑定。
3.使用XmlElement和XmlAttribute
我们可以使用这两个元素来打破XmlAccessorType的规则,主动要求绑定。 例如默认情况下,private的属性是不会被绑定的,下面我们分别使用这两个注解来注释两个私有的属性。
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement
public class Person {
@XmlAttribute
private String id;
@XmlElement
private String name; //这个无参构造器是必须的
public Person(){} public Person(String id, String name){
this.name = name;
this.id = id;
}
}
使用JAXB序列化一个Person对象的结果为:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person id="person_001">
<name>zhangsan</name>
</person>
另外还有一个XmlElements注解。 考虑下面情况:
//这里不能使用接口, JAXB 无法处理接口
public abstract class Animal {} public class Cat extends Animal{
public String color = "red";
} public class Dog extends Animal{
public String size = "big";
} public class Pig extends Animal{
public String weight = "200kg";
} //Person类是JAXB根元素,包含一个Animal的集合
@XmlRootElement
public class Person {
public List<Animal> animals = Arrays.asList(new Dog(), new Cat(), new Pig());
}
我们对一个Person对象序列化后的结构是:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person>
<animals/>
<animals/>
<animals/>
</person>
这很显眼不是我们所希望的,这是因为JAXB会将animals集合中的每一个元素仅仅当做Animal来处理,而不会考虑各种子类的具体情况。我们现在使用XmlElements来修饰animals,让JAXB区别对待各种Animal的子类。
@XmlRootElement
public class Person {
@XmlElements({
@XmlElement(name="dog", type=Dog.class),
@XmlElement(name="cat", type=Cat.class),
@XmlElement(name="pig", type=Pig.class)
})
public List<Animal> animals = Arrays.asList(new Dog(), new Cat(), new Pig());
}
结果为:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person>
<dog>
<size>big</size>
</dog>
<cat>
<color>red</color>
</cat>
<pig>
<weight>200kg</weight>
</pig>
</person>
也许你发现了上面序列化生成的xml并不是最理想的方式,因为对于person对象来讲,它拥有一个animals的集合,可是这个结果却体现不出来这一点。对于集合类型,我们可以使用@XmlElementWrapper来给集合属性增加一个wrapper。 修改Person为:
@XmlRootElement
public class Person {
@XmlElementWrapper(name="animals")
@XmlElements({
@XmlElement(name="dog", type=Dog.class),
@XmlElement(name="cat", type=Cat.class),
@XmlElement(name="pig", type=Pig.class)
})
public List<Animal> animals = Arrays.asList(new Dog(), new Cat(), new Pig());
}
现在的结果就更加理想了:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person>
<animals>
<dog>
<size>big</size>
</dog>
<cat>
<color>red</color>
</cat>
<pig>
<weight>200kg</weight>
</pig>
</animals>
</person>
随机属性和随机元素: @XmlAnyAttribute @XmlAnyElement
我们之前的例子中序列化后产生的xml中无论是attribute还是element 在 java类中都会有具体的 Filed或者 getter/setter 与之对应,但是如果在序列化时我们无法确定java对象到底会有哪些属性,或者 在反序列化的过程中xml文档中的内容是不确定的,我们该怎么办呢? 我们可以使用XmlAnyAttribute 和XmlAnyElement来支持任意随机的attribute和element。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.annotation.XmlAnyAttribute;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.namespace.QName;
import org.w3c.dom.Element; @XmlRootElement
public class Person {
public String name; @XmlAnyAttribute
public Map<QName, String> anyAttribute = new HashMap<>(); @XmlAnyElement
public List<Element> anyElement = new ArrayList<>();
}
现在根元素 Person 就可以支持任意的属性和元素了,值得注意的是XmlAnyElement注释了一个org.w3c.dom.Element的集合,其实就是在处理最原始的dom元素了。 下面是序列化的过程:
import java.io.FileOutputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element; public class Test {
public static void main(String[] args) throws JAXBException, ParserConfigurationException {
JAXBContext context = JAXBContext.newInstance(Person.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); Person person = new Person();
person.name = "zhangsan";
person.anyAttribute.put(new QName("", "id"), "11"); Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element e1 = document.createElement("company");
e1.setTextContent("www.massclouds.com"); person.anyElement.add(e1); try(FileOutputStream out = new FileOutputStream("C:/temp/any.xml")){
marshaller.marshal(person, out);
}catch(Exception e){
e.printStackTrace();
}
}
}
我们在相应的文件中产生了下面的xml内容:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person id="11">
<name>zhangsan</name>
<company>www.massclouds.com</company>
</person>
我们对其进行修改,为person增加一个age属性和一个address子元素,变为下面这样:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person id="11" age="23">
<name>zhangsan</name>
<company>www.massclouds.com</company>
<address>Shandong Jinan</address>
</person>
然后我们对这个xml文档进行反序列化,得到的person对象中就可以包含所有这些随机属性和随机元素了。
public class UnTest {
public static void main(String[] args) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(Person.class);
Unmarshaller u = context.createUnmarshaller();
try(FileInputStream in = new FileInputStream("C:/temp/any.xml")){
Person person = (Person)u.unmarshal(in);
//读取person的随机属性
person.anyAttribute.forEach((key, value) -> System.out.println(key + "-->" + value));
//读取person的随机元素
person.anyElement.forEach(element -> System.out.println(element.getTagName() + "-->" + element.getTextContent()));
}catch(Exception e){
e.printStackTrace();
}
}
}
使用适配器来改变JAXB序列化规则: XmlJavaTypeAdapter
考虑这么一种场景,在java类中我们有一个StringBuffer类型的属性,很显然我们就是希望把它序列化为一个 简单字符串类型,但是JAXB默认是不支持StringBuffer的这种转换的。我们可以使用XmlJavaTypeAdapter来指定一个序列化的适配器,按照我们自己的逻辑来定制序列化规则。
@XmlRootElement
public class Person {
@XmlElement
@XmlJavaTypeAdapter(String2StrBuf.class)
public StringBuffer poem = new StringBuffer(); {
this.poem = new StringBuffer();
//from Dido's Everything to Lose
this.poem.append("I love to be alive ").append("but I was not afraid to die");
}
}
我们在根元素中使用XmlJavaTypeAdapter修饰了一个StringBuffer类型,其中value的值 String2StrBuf就是我们要自己定义的适配器,它必须继承自XmlAdapter。
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class String2StrBuf extends XmlAdapter<String, StringBuffer> {
@Override
public String marshal(StringBuffer strbuf) {
return strbuf.toString();
}
@Override
public StringBuffer unmarshal(String string) {
return new StringBuffer(string);
}
}
从上面的方法签名中我们就可以看出在序列化的过程中调用marshal方法,反之则调用unmarshal。
我们还可以在包级别定义适配器,这样就不需要再包中重复的定义了(以下代码定义在package-info.java中)。
@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters({
@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(type=java.lang.StringBuffer.class, value=com.massclouds.test.String2StrBuf.class)
})
package com.massclouds.test;
有了上面的包级别的定义,在Person中poem属性上的XmlJavaTypeAdapter就可以不要了。
下面我们在来演示一个使用JAXB自带的Adapter来序列化二进制数据的例子,我们将一副图片的二进制数据序列化到一个xml文件中,然后再从xml文件恢复这张图片(虽然现实中这么做有点无聊,^_^)
@XmlRootElement
public class Image {
@XmlJavaTypeAdapter(HexBinaryAdapter.class)
public byte[] data;
}
Image是顶层元素,它的data属性就是我们要序列化到xml文件中的二进制数据,我们看到它使用了HexBinaryAdapter适配器, 这个适配器的作用很明显: 将二进制数据绑定为16进制表示。
public static void main(String[] args) {
try(FileInputStream in = new FileInputStream("C:/6.png");
FileOutputStream xmlOut = new FileOutputStream("C:/temp/data.xml");){
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while((len = in.read(buffer, 0, buffer.length)) != -1){
out.write(buffer, 0, len);
}
Image image = new Image();
image.data = out.toByteArray();
JAXBContext context = JAXBContext.newInstance(Image.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(image, xmlOut);
}catch (Exception e) {
e.printStackTrace();
}
}
上面的代码除去处理流的代码外,剩下的就是JAXB最基本的序列化代码了,也就是说我们并没有做过多其他的处理。生成的xml文件为:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<image>
<!--data的内容就是图片的16进制表示了 -->
<data>191E7721C8A4C714514C10E3C30A777CD1454B18D3C
....
B52F7A28A64A15BA1FA5314EE539ED45142067FFD9</data>
</image>
我们再将xml反序列化,重新得到这幅图片,同样除去流的处理外,只是最基本的JAXB操作而已:
public static void main(String[] args) {
try(FileOutputStream out = new FileOutputStream("C:/temp/image.png");
FileInputStream in = new FileInputStream("C:/temp/data.xml")){
JAXBContext context = JAXBContext.newInstance(Image.class);
Unmarshaller u = context.createUnmarshaller();
Image image = (Image)u.unmarshal(in);
out.write(image.data);
}catch(Exception e){
e.printStackTrace();
}
}
上面我们使用的是图片的byte数据,我们同样也可以结合java 序列化(Serializable)来使用,也就是将一个对象的java序列化数据写入到xml文件中。
对象引用:XmlID, XmlIDREF
考虑这样一种场景: 根元素是 教室, 在一个教室中有许多学生和一个老师(注意始终就只有这一个老师), 这些学生对象都包含一个老师属性,而且都指向教室中的老师, 这个老师同样也有许多学生,也就是教室中的这些学生。
如果我们按照正常的思路去一步一步实现上面这个描述,会出现两个问题:
1. 在序列化的时候将会产生很多重复数据(唯一一个老师却产生了许多老师数据),而在反序列化的时候我们将无法得到我们期待的结果(例如会产生不止一个老师)。
2. 还有可能出现死循环,导致无法序列化。
为了解决上面的问题,我们可以使用XmlID和XmlIDREF来引用对象。其中XmlID必须修饰一个String类型的属性。 下面是具体实现:
public class Teacher {
@XmlID
public String id;
@XmlIDREF
@XmlElementWrapper(name="students")
@XmlElement(name="student_ref")
public List<Student> students;
public Teacher(){}
public Teacher(String id){
this.id = id;
}
}
public class Student {
@XmlID
public String id;
@XmlIDREF
@XmlElement(name="teacher_ref")
public Teacher teacher;
public Student(){}
public Student(String id, Teacher teacher){
this.id = id;
this.teacher = teacher;
}
}
在上面的Teacher和Student中,分别使用XmlID定义了他们各自的ID属性,然后在Student中使用XmlIDREF引用一个Teacher, 而在Teacher中使用XmlIDREF引用一个Student的集合。
@XmlRootElement
public class Classroom {
@XmlElementWrapper(name="students")
@XmlElement(name="student")
public List<Student> students; public Teacher teacher;
}
根元素没有使用 XmlIDREF的原因是我们必须让老师和每一个学生至少完整的出现一次。
public class Test {
public static void main(String[] args) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(Classroom.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Teacher teacher = new Teacher("teacher_001");
Student s1 = new Student("student_001", teacher);
Student s2 = new Student("student_002", teacher);
Student s3 = new Student("student_003", teacher);
teacher.students = new ArrayList<>(Arrays.asList(s1, s2, s3));
Classroom classroom = new Classroom();
classroom.teacher = teacher;
classroom.students = new ArrayList<>(Arrays.asList(s1, s2, s3));
try(FileOutputStream out = new FileOutputStream("C:/temp/ref.xml")){
marshaller.marshal(classroom, out);
}catch(Exception e){
e.printStackTrace();
}
}
}
注意在上面的实现中一共只有一个老师对象和三个学生对象。 序列化的结果为:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<classroom>
<students>
<student>
<id>student_001</id>
<teacher_ref>teacher_001</teacher_ref>
</student>
<student>
<id>student_002</id>
<teacher_ref>teacher_001</teacher_ref>
</student>
<student>
<id>student_003</id>
<teacher_ref>teacher_001</teacher_ref>
</student>
</students>
<teacher>
<id>teacher_001</id>
<students>
<student_ref>student_001</student_ref>
<student_ref>student_002</student_ref>
<student_ref>student_003</student_ref>
</students>
</teacher>
</classroom>
在上面的结果中我们可以清楚的看到 每一个学生和老师的完整数据仅仅出现了一次,其他引用都是仅仅引用了各自的id而已。同样我们对这个结果反序列化,也同样会得到唯一一个老师对象和三个学生对象。
public class UnTest {
public static void main(String[] args) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(Classroom.class);
Unmarshaller u = context.createUnmarshaller();
try(FileInputStream in = new FileInputStream("C:/temp/ref.xml")){
Classroom classroom = (Classroom)u.unmarshal(in);
Teacher teacher = classroom.teacher;
//教室里所有学生的老师对象和 教室自身的老师对象是同一个对象
classroom.students.forEach(student -> System.out.println(student.teacher == teacher));
//教室里所有学生同样也是教室里老师的学生
classroom.students.forEach(student -> System.out.println(teacher.students.contains(student)));
}catch(Exception e){
e.printStackTrace();
}
}
}
JAXB学习(二): 对JAXB支持的主要注解的说明的更多相关文章
- Spring学习(二):Spring支持的5种Bean Scope
序言 Scope是定义Spring如何创建bean的实例的.Spring容器最初提供了两种bean的scope类型:singleton和prototype,但发布2.0以后,又引入了另外三种scope ...
- WCF学习之旅—实现支持REST客户端应用(二十四)
WCF学习之旅—实现REST服务(二十二) WCF学习之旅—实现支持REST服务端应用(二十三) 在上二篇文章中简单介绍了一下RestFul与WCF支持RestFul所提供的方法,及创建一个支持RES ...
- emberjs学习二(ember-data和localstorage_adapter)
emberjs学习二(ember-data和localstorage_adapter) 准备工作 首先我们加入ember-data和ember-localstorage-adapter两个依赖项,使用 ...
- ReactJS入门学习二
ReactJS入门学习二 阅读目录 React的背景和基本原理 理解React.render() 什么是JSX? 为什么要使用JSX? JSX的语法 如何在JSX中如何使用事件 如何在JSX中如何使用 ...
- SpringCloud学习(二):微服务入门实战项目搭建
一.开始使用Spring Cloud实战微服务 1.SpringCloud是什么? 云计算的解决方案?不是 SpringCloud是一个在SpringBoot的基础上构建的一个快速构建分布式系统的工具 ...
- SpringMVC入门学习(二)
SpringMVC入门学习(二) ssm框架 springMVC 在上一篇博客中,我简单介绍了一下SpringMVC的环境配置,和简单的使用,今天我们将进一步的学习下Springmvc的操作. mo ...
- Android JNI学习(二)——实战JNI之“hello world”
本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Nati ...
- day 82 Vue学习二之vue结合项目简单使用、this指向问题
Vue学习二之vue结合项目简单使用.this指向问题 本节目录 一 阶段性项目流程梳理 二 vue切换图片 三 vue中使用ajax 四 vue实现音乐播放器 五 vue的计算属性和监听器 六 ...
- (转)MyBatis框架的学习(二)——MyBatis架构与入门
http://blog.csdn.net/yerenyuan_pku/article/details/71699515 MyBatis框架的架构 MyBatis框架的架构如下图: 下面作简要概述: S ...
- day 81 Vue学习二之vue结合项目简单使用、this指向问题
Vue学习二之vue结合项目简单使用.this指向问题 本节目录 一 阶段性项目流程梳理 二 vue切换图片 三 vue中使用ajax 四 vue实现音乐播放器 五 vue的计算属性和监听器 六 ...
随机推荐
- 解决[BScroll warn]: Can not resolve the wrapper DOM. Vue better-scroll
在开发项目过程中,使用better-scroll插件中遇到了滚动一次重复提示相同错误 [BScroll warn]: Can not resolve the wrapper DOM. Vue bett ...
- ProGuard使用文档
介绍 是一个对于Java字节码的免费的压缩器,优化器,混淆器和审核器: l 它检测并删除未使用的类,字段,方法和属性. l 它优化字节码并删除未使用的指令. l 它重命名其余类.字段和方法使用短 ...
- 什么是ZooKeeper?ZooKeeper分布式事务详解
前言 上一章我们了解了zookeeper到底是什么,这一章重点来看zookeeper当初到底面临什么问题? 而zookeeper又是如何解决这些问题的? 实际上zookeeper主要就是解决分布式环境 ...
- [leetcode]297. Serialize and Deserialize Binary Tree一般二叉树的编解码
由于一般的前序遍历不能唯一的还原出原本你的二叉树,所以要改变一下: 记录二叉树的结构信息,也就是空节点用符号表示 一般的前序遍历只是记录了节点的前后顺序,通过记录空节点,每一层的结构就可以记录下来 解 ...
- libzip开发笔记(一):libzip库介绍、编译和工程模板
前言 Qt使用一些压缩解压功能,选择libzip库,libzip库比较原始,也是很多其他库的基础支撑库. libzip libzip是一个C库,用于读取,创建和修改zip档案.可以从数 ...
- ESP32 BLE蓝牙 微信小程序通信发送大于20字符数据
由于微信小程序只支持BLE每次发送数据不大于20个字节,ESP32则有经典蓝牙.低功耗蓝牙两种模式. 要解决发送数据大于20个字节的问题,最简单实用的方式就是分包发送.如下图所示: 1.什么起始字符和 ...
- 「每日一题」有人上次在dy面试,面试官问我:vue数据绑定的实现原理。你说我该如何回答?
关注「松宝写代码」,精选好文,每日一题 时间永远是自己的 每分每秒也都是为自己的将来铺垫和增值 作者:saucxs | songEagle 来源:原创 一.前言 文章首发在「松宝写代码」 2020. ...
- Java GC --- Java堆内存
Java堆是被所有线程共享的一块内存区域,所有对象实例和数组都在堆上进行内存分配.为了进行高效的垃圾回收,虚拟机把堆内存划分成: 1. 新生代(Young Generation): 由 Eden 与 ...
- Angular入门到精通系列教程(4)- 开发环境搭建以及入手项目
1. 本地开发环境搭建 1.1. node.js 1.2. Angular CLI 2. 开发工具 - Visual Studio Code 第一个Anuglar项目 创建第一个anuglar项目 A ...
- postgresql-从表中随机获取一条记录
目录 postgresql如何从表中高效的随机获取一条记录 随机获取一条记录random() 改写1 改写2 改写3 对比 注意 结语 postgresql如何从表中高效的随机获取一条记录 selec ...