前言:
最近一直在做各种接口的对接,接触最多的数据类型就是JSON和XML数据,还有XML中包含JSON的数据,而在Java中对象和XML之间的转换经常用到JAXB注解,抽空在这里总结一下,首先做一下准备工作
测试类代码:
@XmlRootElement
public class Student {
private String name; // 姓名
private String sex; // 性别
private int number; // 学号
private String className; // 班级
public Student(){}
public Student(String string, String string2, int i, String string3) {
this.name = string;
this.sex = string2;
this.className = string3;
}
@XmlElement(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlElement(name = "sex")
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@XmlElement(name = "number")
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@XmlElement(name = "className")
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
工具类代码:
public class XStreamUtil {
/**
* 扩展xstream,使其支持CDATA块
* 整数和浮点数不添加
* @date 2013-05-19
*/
public static XStream xstream2 = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@SuppressWarnings("rawtypes")
public void startNode(String name, Class clazz) {
if(!name.equals("xml")){
char[] arr = name.toCharArray();
if (arr[0] >= 'a' && arr[0] <= 'z') {
//arr[0] -= 'a' - 'A';
//ASCII码,大写字母和小写字符之间数值上差32
arr[0] = (char) ((int) arr[0] - 32);
}
name = new String(arr);//char数组转字符串
}
super.startNode(name, clazz);
}
@Override
public void setValue(String text) {
if(text!=null && !"".equals(text)){
if(text.matches("[0-9]*(\\.?)[0-9]*") || text.matches("[0-9]*(\\.?)[0-9]*")){//如果是正式或者浮点数
cdata = false;
}else{
cdata = true;
}
}
super.setValue(text);
}
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("");
} else {
writer.write(text);
}
}
};
}
});
/**
* 扩展xstream,使其支持CDATA块
*
* @date 2013-05-19
*/
public static XStream xstream = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@SuppressWarnings("rawtypes")
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("");
} else {
writer.write(text);
}
}
};
}
});
/**
* 将XML内容转换成对象
*/
@SuppressWarnings("unchecked")
public static T unmarshal(String xml, Class clazz) throws JAXBException{
JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
return (T)unmarshaller.unmarshal(new StringReader(xml));
}
/**
* 将对象转换成XML
*/
public static String marshal(Object object, Class> clazz) throws JAXBException{
JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
StringWriter writer = new StringWriter();
marshaller.marshal(object, writer);
return writer.toString();
}
/**
* 将java对象转换为xml
* @param
* @param reqTextMessage
* @return
*/
public static String JavaToXml(T t){
xstream.alias("xml", t.getClass());
return xstream.toXML(t);
}
}
测试代码:
public class Test {
public static void main(String[] args) throws JAXBException {
Student st = new Student("张三","男",10001,"尖");
String xml = XStreamUtil.marshal(st, Student.class);
System.out.println(StringUtils.formatXml(xml));
}
}
一、@XmlRootElement:
类级别的注解,将类映射为xml全局元素,也就是根元素。就像spring配置文件中的beans
@XmlRootElement
public class Student {
private String name; // 姓名
private String sex; // 性别
private int number; // 学号
private String className; // 班级
public Student(){}
public Student(String string, String string2, int i, String string3) {
this.name = string;
this.sex = string2;
this.className = string3;
}
测试结果:
尖
张三
0
男
二、@XmlAccessorType :
包和类级别的注解,javaEE的API对该注解的解释是:控制字段是否被默认序列化。通俗来讲,就是决定哪些字段或哪些get/set方法对应的字段会被映射为xml元素,需要注意的是字段或get/set方法的访问权限(public/private)会影响字段是否被映射为xml元素,下面会详细讲解。
注解只有一个value属性,可取的值是一个名为XmlAccessType的枚举类型里的值,下面详细看一下这几个值分别有什么用:
2.1、XmlAccessType.PROPERTY:
理解:下面是源码里面的一段描述,这一段的大致翻译是,《JAXB绑定类中的每个getter/setter对都将自动绑定到XML,除非由@link xmltinate注释,只有当字段被一些JAXB注释显式注释时,字段才绑定到XML》(注:这一段话需要格外注意的是,,该属性可以自动将每个getter/setter绑定到xml,但不会自动绑定字段到XML)
/**
* Every getter/setter pair in a JAXB-bound class will be automatically
* bound to XML, unless annotated by {@link XmlTransient}.
*
* Fields are bound to XML only when they are explicitly annotated
* by some of the JAXB annotations.
*/
补充:
(1)当使用了该值,只要字段有对应的get/set方法对(注意是成对出现,只有其中一个不会发生映射),不需要使用@XmlElement注解,不论该方法的访问权限是什么(即使是private),jaxb就会将该字段映射成xml元素。不过最好加上@XmlElement注解,get/set方法任选一个即可,都加上会报错。
(2)若在一个字段有set/get方法对但又在字段上添加@XmlElement注解会报属性重复的错误
(3)若没有set/get方法对,则需要在字段上使用@XmlElement注解才可以映射为xml元素,否则不会发生映射
(4)若get/set方法上使用了@XmlTransient注解,但想要对应字段发生映射,需要在对应字段上添加@XmlElement注解,此时不会报错,并将该字段映射为xml元素。
2.2、XmlAccessType.FIELD:
理解:下面是源码中的解释,这一段的大致翻译是,《jaxb绑定类中的每个非静态、非瞬态字段都将自动绑定到XML,除非使用@XmlTransient进行注释,只有当某些JAXB注释显式地对getter/setter对进行注释时,它们才会绑定到XML》(注:这段需要注意的是,该属性可以自动绑定类中的非静态、非瞬态字段,但不会自动绑定getter/setter方法,正好与XmlAccessType.PROPERTY相反)
/**
* Every non static, non transient field in a JAXB-bound class will be automatically
* bound to XML, unless annotated by {@link XmlTransient}.
*
* Getter/setter pairs are bound to XML only when they are explicitly annotated
* by some of the JAXB annotations.
*/
补充:
(1)每个非静态的字段(无论访问权限如何)都会被jaxb映射为xml元素,即使没有get/set方法对,即使没有使用@XmlElement元素,但最好加上该注解以表明该字段要被映射为xml元素
(2)虽然没有get/set方法对,也会发生映射,但加上get/set方法对也不会报错,因为我们经常会使用这两个方法。但注意,不能再在这两个方法上使用@XmlElement方法,否则会报属性重复的错误。
(3)若在字段上使用了@XmlTransient注解,但还想让该字段发生映射,需要在该字段对应的get/set方法上添加@XmlElement
2.3、XmlAccessType.PUBLIC_MEMBER (该值为默认值):
注:如果不指定@XmlAccessorType的value值或者没有使用此注解,在转xml时会默认为value为XmlAccessType.PROPERTY,由下面的截图可以清晰的看出默认值是XmlAccessType.PROPERTY,
@Inherited @Retention(RUNTIME) @Target({PACKAGE, TYPE})
public @interface XmlAccessorType {
/**
* Specifies whether fields or properties are serialized.
*
* @see XmlAccessType
*/
XmlAccessType value() default XmlAccessType.PUBLIC_MEMBER;
}
理解:下面则是对此的描述,翻译大概是《每个公共getter/setter对和每个公共字段都将自动绑定到XML,除非由@link xmltinate批注,私有、受保护或、默认为“仅包访问”仅在以下情况下绑定到XML,由适当的JAXB注释显式注释。》(注:在这里要格外注意XmlAccessType.PUBLIC_MEMBER属性针对的是java对象中所有的public访问权限的成员变量和通过getter/setter方式访问的成员变量,而私有或者仅包访问权限的字段,并不会自动绑定到XML)
/**
* Every public getter/setter pair and every public field will be
* automatically bound to XML, unless annotated by {@link XmlTransient}.
*
* Fields or getter/setter pairs that are private, protected, or
* defaulted to package-only access are bound to XML only when they are
* explicitly annotated by the appropriate JAXB annotations.
*/
补充:
(1)每个访问权限为public的字段,或者每个访问权限为public的get/set方法对,都会将字段映射为xml元素,即使不使用@XmlElement,但最好加上。不可同时存在public字段和对应的get/set方法对,不然会报属性重复的错误
(2)若使用@XmlElement注解,则实体类(注:实体类中的字段为私有private)中不能存在get/set方法或者只能在get/set上使用,否则会报属性重复的错误
(3)若字段不为public,get/set方法为public并使用了@XmlTransient,需要在字段上添加@XmlElement才会发生映射,若字段为public并使用了@XmlTransient,get/set方法对不为public,需要在get/set方法上使用@XmlElement才会映射。
2.4、XmlAccessType.NONE:
注: 这一段的翻译是《任何字段或属性都不会绑定到XML,除非使用某些JAXB注释对它们进行特别注释。》
/**
* None of the fields or properties is bound to XML unless they
* are specifically annotated with some of the JAXB annotations.
*/
补充:
任何字段,get/set方法对都不会发生映射,除非使用某些注解,如@XmlElement,@XmlElementWrapper等。
三、@XmlElement:
字段,方法,参数级别的注解。该注解可以将被注解的字段(非静态),或者被注解的get/set方法对应的字段映射为本地元素,也就是子元素。默认使用字段名或get/set方法去掉前缀剩下部分小写作为元素名(在字段名和get/set方法符合命名规范的情况下)。
属性:该注解的属性常用的属性有如下
(1)defaultValue:可以指定该元素默认的文本值
(2)namespace:可以指定该元素所属的命名空间
(3)name: 同@XmlRootElement注解的name属性一样
(4)required:可以指定该元素是否必须出现,默认为false
(5)nillable: 可以指定元素的文本值是否可以为空,默认为false
@XmlRootElement(name = "Student")
@XmlAccessorType(XmlAccessType.FIELD)
public class Student {
@XmlElement(name = "name",defaultValue = "hefeng")
private String name; // 姓名
@XmlElement(name = "sex", namespace = "Student")
private String sex; // 性别
@XmlElement(name = "number", required = true)
private int number; // 学号
@XmlElement(name = "className", nillable = true)
private String className; // 班级
public Student(){}
public Student(String string, String string2, int i, String string3) {
this.name = string;
this.sex = string2;
this.className = string3;
}
测试结果:
张三
男
0
尖
四、@XmlAttribute:
字段和方法级别的注解。该注解会将字段或get/set方法对应的字段映射成本类对应元素的属性,属性名默认使用字段名或get/set方法去掉前缀剩下部分首字母小写(在字段名和get/set方法符合命名规范的情况下)。修改上面例子:
属性:该注解有name,required,namespace三个属性。用法和@XmlElement注解相同
@XmlRootElement(name = "Student")
@XmlAccessorType()
public class Student {
private String name; // 姓名
private String sex; // 性别
private int number; // 学号
private String className; // 班级
public Student(){}
public Student(String string, String string2, int i, String string3) {
this.name = string;
this.sex = string2;
this.className = string3;
}
@XmlAttribute(name="shiqingxue")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
测试结果:
尖
0
男
五、@XmlAccessorOrder:
包和类级别的注解。控制生成元素的顺序。
属性:该属性有XmlAccessOrder.ALPHABETICAL 和 XmlAccessOrder.UNDEFINED两种
(1)XmlAccessOrder.ALPHABETICAL,代表按照字母表的顺序对生成的元素排序,也就是我们常说的字典顺序
(2)XmlAccessOrder.UNDEFINED,代表按照类中字段的顺序生成元素的顺序,也是该注解的默认值
测试XmlAccessOrder.UNDEFINED:
@XmlRootElement(name = "Student")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlAccessorOrder()
public class Student {
@XmlElement(name = "NAME")
private String name; // 姓名
@XmlElement(name = "SEX")
private String sex; // 性别
@XmlElement(name = "NUMBER")
private int number; // 学号
@XmlElement(name = "CLASS_NAME")
private String className; // 班级
public Student(){}
public Student(String string, String string2, int i, String string3) {
this.name = string;
this.sex = string2;
this.className = string3;
}
测试结果:可以看出字段的输出顺序是按照类中字段和属性的顺序
张三
男
0
尖
测试XmlAccessOrder.ALPHABETICAL:
@XmlRootElement(name = "Student")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
public class Student {
@XmlElement(name = "NAME")
private String name; // 姓名
@XmlElement(name = "SEX")
private String sex; // 性别
@XmlElement(name = "NUMBER")
private int number; // 学号
@XmlElement(name = "CLASS_NAME")
private String className; // 班级
public Student(){}
public Student(String string, String string2, int i, String string3) {
this.name = string;
this.sex = string2;
this.className = string3;
}
测试结果:可以看出字段的输出是按照字典顺序排序的:
尖
张三
0
男
六、@XmlElementWrapper
字段和方法级别的注解:围绕被映射的xml元素生成包装元素。主要用在集合对象映射后生成包装映射结果的xml元素,来看一下例子,创建两个类Children、Father。
@XmlAccessorType(XmlAccessType.FIELD)
public class Children{
@XmlElement(name = "USER_NAME")
private String userName;
public Children(){};
public Children(String userName){
this.userName = userName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
@XmlRootElement(name = "Father")
@XmlAccessorType(XmlAccessType.FIELD)
public class Father {
@XmlElement(name = "AGE")
private Integer age;
@XmlElement(name = "Children")
private List childrenList = new ArrayList();
public Father(){}
public Father(Integer age){
this.age = age;
childrenList.add(new Children("逝清雪"));
childrenList.add(new Children("莫问"));
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
测试代码:
public class Test {
public static void main(String[] args) throws JAXBException {
Father st = new Father(12);
String xml = XStreamUtil.marshal(st, Father.class);
System.out.println(StringUtils.formatXml(xml));
}
}
在没有@XmlElementWrapper注解下的测试结果:
12
逝清雪
莫问
有@XmlElementWrapper注解下的测试结果:
12
逝清雪
莫问
七、@XmlJavaTypeAdapter
包、类、字段,方法、参数级别的注解:解决java日期(Date),数字(Number)格式化问题。直接看例子,修改Person类,添加一个Date类型字段:
在这里要向使用@XmlJavaTypeAdapter我们就要指定一个指向将值类型转换为绑定类型的类,这个类需要继承XmlAdapter抽象类重写里面的unmarshal和marshal方法
DateAdapter工具类:
public class DateAdapter extends XmlAdapter {
private SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public String marshal(Date v) throws Exception {
return dateFormat.format(v);
}
@Override
public Date unmarshal(String v) throws Exception {
return dateFormat.parse(v);
}
}
测试代码:
@XmlRootElement(name = "Children")
@XmlAccessorType(XmlAccessType.FIELD)
public class Children{
@XmlElement(name = "USER_NAME")
private String userName;
@XmlJavaTypeAdapter(DateAdapter.class)
@XmlElement(name = "system_date")
private Date systemDate;
public Children(){};
public Children(String userName, Date data){
this.userName = userName;
this.systemDate = data;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Date getSystemDate() {
return systemDate;
}
public void setSystemDate(Date systemDate) {
this.systemDate = systemDate;
}
}
测试代码:
public class Test {
public static void main(String[] args) throws JAXBException {
Children st = new Children("逝清雪", new Date());
String xml = XStreamUtil.marshal(st, Children.class);
System.out.println(StringUtils.formatXml(xml));
}
}
测试结果:
逝清雪
2019-04-11 11:19:24
八、@XmlTransient:
类,字段,方法级别的注解:可使JAXB在映射xml元素时忽略被注解的类,字段,get/set对应字段。需要注意的是该注解与所有其他JAXB注释相互排斥,也就是说与其他注释连用就会报错
@XmlRootElement(name = "Children")
@XmlAccessorType(XmlAccessType.FIELD)
public class Children{
@XmlElement(name = "USER_NAME")
private String userName;
@XmlTransient
private Date systemDate;
public Children(){};
public Children(String userName, Date data){
this.userName = userName;
this.systemDate = data;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Date getSystemDate() {
return systemDate;
}
public void setSystemDate(Date systemDate) {
this.systemDate = systemDate;
}
}
测试代码:
public class Test {
public static void main(String[] args) throws JAXBException {
Children st = new Children("逝清雪", new Date());
String xml = XStreamUtil.marshal(st, Children.class);
System.out.println(StringUtils.formatXml(xml));
}
}
测试结果:
逝清雪