用来记录一些原创性的总结
通常我们在使用JAXB生成XML时,都是通过@XmlRootElement
或@XmlElement
事先指定对应的类型的对象在生成XML时生成的元素的名称。比如下面这样。
public class SpecifyNameTest {
@Test
public void test() {
User user = new User();
user.setId(2);
user.setName("张三");
JAXB.marshal(user, System.out);
}
@XmlRootElement(name="User")
public static class User {
private Integer id;
private String name;
@XmlAttribute(name="id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@XmlElement(name="Name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
对应的输出结果是如下这样:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<User id="2">
<Name>张三</Name>
</User>
那如果现在业务上有这样一项需求,要求用户ID能够被2整除时生成的Xml根节点是“user_2”,否则生成的根节点是“user_1”(笔者这里只是简单的打个比方而已,虽然这个举例非常不切实际,但是实际的业务场景下可能由于遗留原因等情况,是会有动态生成Xml元素名称的需求的)。那这个时候应该怎么办呢?这个时候我们应该把我们的对象转换为一个JAXBElement
对象,JAXB在把JAXBElement
类型的对象转换为XML时会调用它的getName()
方法获取对应的元素的名称。具体示例如下。
public class SpecifyNameTest {
@Test
public void test1() {
ObjectFactory objectFactory = new ObjectFactory();
User user = new User();
user.setId(2);
user.setName("张三");
JAXBElement<User> userEle = objectFactory.createUser(user);
JAXB.marshal(userEle, System.out);
}
public static class ObjectFactory {
public JAXBElement<User> createUser(User user) {
String localPart = null;//元素名称
if (user.getId()%2 == 0) {
localPart = "user_2";
} else {
localPart = "user_1";
}
QName name = new QName(localPart);
JAXBElement<User> userEle = new JAXBElement<>(name, User.class, user);
return userEle;
}
}
@XmlRootElement(name="User")
public static class User {
private Integer id;
private String name;
@XmlAttribute(name="id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@XmlElement(name="Name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
这个时候User
类上的@XmlRootElement
已经不起作用了,可有可无,但是属性上的@XmlAttribute
和@XmlElement
还是会起作用的。生成的XML如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<user_2 id="2">
<Name>张三</Name>
</user_2>
有时候可能我们的一个类里面的某个属性生成的XML元素也需要动态化,那应该怎么做呢?现假设我们在原来的User
类上增加一个deptName
属性,我们希望在生成它对应的XML元素时,当用户的ID能被2整除时,生成的元素名称为“dept_2”,否则为“dept_1”。那你会不会觉得这很简单,我把对应的属性定义为JAXBElement
类型即可,像如下这样子。
public static class ObjectFactory {
public JAXBElement<String> createDept(User user) {
String deptEleName = null;//部门对应的元素名称
if (user.getId() % 2 == 0) {
deptEleName = "dept_2";
} else {
deptEleName = "dept_1";
}
QName deptEleQName = new QName(deptEleName);
JAXBElement<String> deptEle = new JAXBElement<>(deptEleQName, String.class, user.getDeptName());
return deptEle;
}
}
@XmlRootElement(name="User")
public static class User {
private Integer id;
private String name;
private String deptName;
private JAXBElement<String> dept;
@XmlAttribute(name="id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@XmlElement(name="Name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlTransient
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public JAXBElement<String> getDept() {
return dept;
}
public void setDept(JAXBElement<String> dept) {
this.dept = dept;
}
}
针对上面的定义,我们进行如下测试。
@Test
public void test2() {
ObjectFactory objectFactory = new ObjectFactory();
User user = new User();
user.setId(2);
user.setName("张三");
user.setDeptName("部门1");
JAXBElement<String> dept = objectFactory.createDept(user);
user.setDept(dept);
JAXB.marshal(user, System.out);
}
上面的测试代码的输出结果如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<User id="2">
<dept>
<nil>false</nil>
<value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">部门1</value>
</dept>
<Name>张三</Name>
</User>
显然,这与我们预想的结果是不一样的,我们本来预想的是得到如下这样的内容。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<User id="2">
<dept_2>部门1</dept_2>
<Name>张三</Name>
</User>
那需要如何才能得到我们想要的结果呢?这时候需要介绍几个新的注解,@XmlRegistry
、@XmlElementDecl
和@XmlElementRef
。
XmlRegistry
注解用来标注在类上面,表示该类中包含有使用@XmlElementDecl
标注的方法。
XmlElementDecl
注解用来标注在方法上,该方法需要接收一个参数,且需要有返回类型。它又需要与@XmlElementRef
一起使用。XmlElementDecl
在使用时必须指定一个name
属性,namespace
属性也可以指定,以唯一的确定这样一个XmlElementDecl
的声明。
XmlElementRef
注解一共有两种用法,本文只介绍其与XmlElementDecl
一起使用的用法。我们可以通过XmlElementRef
来指定当前的XML
和对象之间的映射需要使用的是哪个XmlElementDecl
指定的方法,这是通过其name
属性和namespace
属性来指定的,它们的值必须与对应的XmlElementDecl
上声明的name
和namespace
一致。
采用了@XmlElementRef
标注后,对应的对象转换XML
形式就不会以当前的属性声明为准了,而是会以实际传递过来的值为准了。当我们的一个属性声明为JAXBElement
形式,我们希望它生成的XML
形式是以JAXBElement
中包含的那个具体的对象为准,而不是以JAXBElement
自己为准时,我们可以在该属性上使用@XmlElementRef
标注,同时指向创建它的ObjectFactory
的对应方法上标注的@XmlElementDecl
,同时需要注意在ObjectFactory
类上采用@XmlRegistry
进行标注。然后在转换XML时,还需要指定使用的Class
除了当前的对象外,还包括对应的ObjectFactory
类。所以针对上面的需求,我们可以改为如下配置即可。
@XmlRegistry
public static class ObjectFactory {
@XmlElementDecl(name="user.dept")
public JAXBElement<String> createDept(User user) {
String deptEleName = null;//部门对应的元素名称
if (user.getId() % 2 == 0) {
deptEleName = "dept_2";
} else {
deptEleName = "dept_1";
}
QName deptEleQName = new QName(deptEleName);
JAXBElement<String> deptEle = new JAXBElement<>(deptEleQName, String.class, user.getDeptName());
return deptEle;
}
}
@XmlRootElement(name="User")
public static class User {
private Integer id;
private String name;
private String deptName;
private JAXBElement<String> dept;
@XmlAttribute(name="id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@XmlElement(name="Name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlTransient
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
@XmlElementRef(name="user.dept")
public JAXBElement<String> getDept() {
return dept;
}
public void setDept(JAXBElement<String> dept) {
this.dept = dept;
}
}
在上面的代码中,我们在ObjectFactory
类上加上了@XmlRegistry
,在对应的createDept()
方法上加上了@XmlElementDecl(name="user.dept")
,同时还在User
的getDept()
方法上加上了@XmlElementRef(name="user.dept")
。对应的测试代码我们也调整如下,因为我们在转换XML
时还需要加入ObjectFactory.class
,所以我们改用JAXBContext
创建Marshaller
的方式进行对象和XML
之间的转换。
@Test
public void test2() throws Exception {
ObjectFactory objectFactory = new ObjectFactory();
User user = new User();
user.setId(2);
user.setName("张三");
user.setDeptName("部门1");
JAXBElement<String> dept = objectFactory.createDept(user);
user.setDept(dept);
JAXBContext jaxbContext = JAXBContext.newInstance(User.class, ObjectFactory.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(user, System.out);
}
应用上面的测试代码,最终的生成结果是如下这样的,这就达到了我们的期望。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<User id="2">
<dept_2>部门1</dept_2>
<Name>张三</Name>
</User>
上面给出的示例是基于一个普通属性的,其实对于一个复杂类型的对象也是一样的。比如我们把上面的代码改改,我们把dept
改为Dept
对象,我们希望生成该对象对应的XML
元素时对应的元素名是Dept
对象的id
和name
通过下划线连起来的样子。具体如下所示。
@XmlRegistry
public static class ObjectFactory {
@XmlElementDecl(name = "dept")
public JAXBElement<Dept> createDept(Dept dept) {
String deptEleName = dept.getId() + "_" + dept.getName();// 部门对应的元素名称
QName deptEleQName = new QName(deptEleName);
JAXBElement<Dept> deptEle = new JAXBElement<>(deptEleQName, Dept.class, dept);
return deptEle;
}
}
@XmlRootElement(name = "User")
public static class User {
private Integer id;
private String name;
private JAXBElement<Dept> dept;
@XmlAttribute(name = "id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@XmlElement(name = "Name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlElementRef(name = "dept")
public JAXBElement<Dept> getDept() {
return dept;
}
public void setDept(JAXBElement<Dept> dept) {
this.dept = dept;
}
}
public static class Dept {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后把我们的测试代码也调整一下,调整成如下这样。
@Test
public void test2() throws Exception {
ObjectFactory objectFactory = new ObjectFactory();
User user = new User();
user.setId(2);
user.setName("张三");
Dept dept = new Dept();
dept.setId(1);
dept.setName("技术部");
JAXBElement<Dept> deptEle = objectFactory.createDept(dept);
user.setDept(deptEle);
JAXBContext jaxbContext = JAXBContext.newInstance(User.class, ObjectFactory.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(user, System.out);
}
结果输出如下所示,也转换了。对象里面原来应该是怎么转换的还是怎么转换的,跟其它JAXB
配置方式是一样的。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<User id="2">
<1_技术部>
<id>1</id>
<name>技术部</name>
</1_技术部>
<Name>张三</Name>
</User>
需要说明的是我们定义的ObjectFactory中的方法在unmarshal时是不会调用的,进行marshal时也不会被自动调用,它的主要作用是持有@XmlRegistry和@XmlElementDecl的声明。
(本文由Elim写于2017年6月9日)