JAXB生成XML时指定以子类的结构生成XML
假设现在有这样一项任务,要求你写两个关于获取User和Dept的接口出来,它们对外提供的数据是XML格式,分别对应如下格式。
<response>
<errorCode>0</errorCode>
<errorMessage>成功</errorMessage>
<data>
<dept>
<id>100</id>
<name>财务部</name>
<parentId>10</parentId>
<no>A001</no>
</dept>
</data>
</response>
<response>
<errorCode>0</errorCode>
<errorMessage>成功</errorMessage>
<data>
<user>
<id>1000</id>
<name>张三</name>
<parentId>100</parentId>
<username>zhangsan</username>
</user>
</data>
</response>
从response到data那部分其实是系统中对外接口的通用格式,所不同的内容都是data元素里面包含的内容,不单是上面两个接口的响应格式,其它接口也是一样的。通用格式如下所示:
<response>
<errorCode>0</errorCode>
<errorMessage>成功</errorMessage>
<data>
<!--特性的内容-->
</data>
</response>
关于User和Dept接口对应的Java类,系统中已经存在了相应的定义,定义如下:
/**
* 抽象的组织机构,提取出公用的id、父级id和名称
*/
public abstract class AbstractOrg {
public static final int TYPE_DEPT = 1;
public static final int TYPE_USER = 2;
private Integer id;
private Integer parentId;
private String name;
/**
* 区分组织类型的,1是Dept,2是User
*/
private final int type;
protected AbstractOrg(int type) {
this.type = type;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getParentId() {
return parentId;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getType() {
return type;
}
}
public class Dept extends AbstractOrg {
private String no;
public Dept() {
super(AbstractOrg.TYPE_DEPT);
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
}
public class User extends AbstractOrg {
private String username;
public User() {
super(AbstractOrg.TYPE_USER);
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
为了满足上述的接口需求,通用的格式对应的Java类定义我们可能会定义如下,定义了Response类和ResultData类,真实的data部分将由ResultData持有。
/**
* 根节点
*/
@XmlRootElement
@XmlType(propOrder= {"errorCode", "errorMessage", "data"})
public class Response {
private String errorCode;
private String errorMessage;
private ResultData data;
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public ResultData getData() {
return data;
}
public void setData(ResultData data) {
this.data = data;
}
}
/**
* data节点包装对象的包装类
*/
public class ResultData {
private Object realData;
public ResultData() {}
public ResultData(Object realData) {
this.realData = realData;
}
public void setRealData(Object realData) {
this.realData = realData;
}
@XmlElement
public Object getRealData() {
return realData;
}
}
这样Dept对应的接口生成的XML可以进行类似如下的调用,在创建对应的JAXBContext时必须传递realData对应的真实Class,因为通过Response关联到的ResultData中定义的realData的类型是java.lang.Object。不传递realData的真实类型时,JAXB遇到realData会不知道如何去解析它。
@Test
public void testMarshalDept() throws Exception {
Dept dept = new Dept();
dept.setId(100);
dept.setParentId(10);
dept.setName("财务部");
dept.setNo("A001");
this.marshal(dept);
}
private void marshal(Object realData) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(Response.class, realData.getClass());
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Response response = new Response();
response.setErrorCode("0");
response.setErrorMessage("成功");
ResultData data = new ResultData(realData);
response.setData(data);
marshaller.marshal(response, System.out);
}
上面的测试代码生成的XML如下:
<response>
<errorCode>0</errorCode>
<errorMessage>成功</errorMessage>
<data>
<realData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="dept">
<id>100</id>
<name>财务部</name>
<parentId>10</parentId>
<no>A001</no>
</realData>
</data>
</response>
从生成的XML我们可以看到生成的XML的结构确实是按照传递的realData的真实类型的结构生成的,比如上面就是按照传递的Dept类型的对应的结构生成的。但是有两个问题,一是realData节点上多了namespace的引用,二是realData节点的名称不是我们想要的dept,而是默认的realData。对于问题一来说,因为顺着根节点类型Response是没有直接关联Dept的,相对于Response来说,Dept是外部引入的,不是它内部直接关联的,所以在realData节点上有了类型的声明。对于问题二有三种解决方案,这三种解决方案都可以同时解决问题一和问题2,本文将介绍其中的两种方案,第三种方案在后续介绍动态根据对象属性生成节点名称时会讲到。
方案一
可能有读者会想到@XmlElement可以用来指定生成XML时元素的名称,而不是使用默认的属性名称。可能会想我们可以把ResultData上的@XmlElement改为@XmlElement(name="dept"),这样生成的节点名称就是dept了。
public class ResultData {
private Object realData;
public ResultData() {}
public ResultData(Object realData) {
this.realData = realData;
}
public void setRealData(Object realData) {
this.realData = realData;
}
@XmlElement(name="dept")
public Object getRealData() {
return realData;
}
}
调整后生成的XML如下:
<response>
<errorCode>0</errorCode>
<errorMessage>成功</errorMessage>
<data>
<dept xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="dept">
<id>100</id>
<name>财务部</name>
<parentId>10</parentId>
<no>A001</no>
</dept>
</data>
</response>
这样看着好像是解决了节点名称的问题,实际上还是不可行的,上述我们把节点名称写死了,已经明确的指定为dept了,如果realData传递的是User类型的对象,生成的节点名称也会是dept,而不是我们期望的user。所以这么做肯定是不行的。需要动态的使用节点的名称。这个时候我们就可以把@XmlElement和@XmlElements一起使用,即定义多个@XmlElement,指定在realData为Dept类型时,生成的节点名称为dept,realData为User类型时,生成的节点名称为user。对ResultData的realData配置调整如下。
public class ResultData {
private Object realData;
public ResultData() {}
public ResultData(Object realData) {
this.realData = realData;
}
public void setRealData(Object realData) {
this.realData = realData;
}
@XmlElements({ @XmlElement(name = "dept", type = Dept.class), @XmlElement(name = "user", type = User.class) })
public Object getRealData() {
return realData;
}
}
这时候生成出来的XML就是我们期望的样子。传递Dept对象生成的结果如下:
<response>
<errorCode>0</errorCode>
<errorMessage>成功</errorMessage>
<data>
<dept>
<id>100</id>
<name>财务部</name>
<parentId>10</parentId>
<no>A001</no>
</dept>
</data>
</response>
传递User对象生成的结果如下:
<response>
<errorCode>0</errorCode>
<errorMessage>成功</errorMessage>
<data>
<user>
<id>1000</id>
<name>张三</name>
<parentId>100</parentId>
<username>zhangsan</username>
</user>
</data>
</response>
此方案的完整配置如下:
/**
* 根节点
*/
@XmlRootElement
@XmlType(propOrder= {"errorCode", "errorMessage", "data"})
public class Response {
private String errorCode;
private String errorMessage;
private ResultData data;
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public ResultData getData() {
return data;
}
public void setData(ResultData data) {
this.data = data;
}
}
/**
* data节点包装对象的包装类
*/
public class ResultData {
private Object realData;
public ResultData() {}
public ResultData(Object realData) {
this.realData = realData;
}
public void setRealData(Object realData) {
this.realData = realData;
}
@XmlElements({ @XmlElement(name = "dept", type = Dept.class), @XmlElement(name = "user", type = User.class) })
public Object getRealData() {
return realData;
}
}
/**
* 抽象的组织机构,提取出公用的id、父级id和名称
*/
public abstract class AbstractOrg {
public static final int TYPE_DEPT = 1;
public static final int TYPE_USER = 2;
private Integer id;
private Integer parentId;
private String name;
/**
* 区分组织类型的,1是Dept,2是User
*/
private final int type;
protected AbstractOrg(int type) {
this.type = type;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getParentId() {
return parentId;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getType() {
return type;
}
}
public class Dept extends AbstractOrg {
private String no;
public Dept() {
super(AbstractOrg.TYPE_DEPT);
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
}
public class User extends AbstractOrg {
private String username;
public User() {
super(AbstractOrg.TYPE_USER);
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
使用此方案时我们在创建JAXBContext时可以不用再传递realData真实的类型了,因为它们都已经通过getRealData()方法上的@XmlElements注解定义了,JAXBContext通过ResultData就可以识别它们了。另外此方案在realData的类型比较少的时候可以很方便的进行配置,如果在一个比较大型的接口系统中,realData会有好几百或更多的可能性,把它们都定义在realData上有点不太现实。幸好接下来要介绍的方案二可以解决这种问题。
方案二
方案二是使用@XmlElementRef注解,使用该注解标注在getRealData()方案上可以使用JAXB在生成XML时自动使用当前定义类型的实际子类型的结构来生成。但是它标注的属性或方法的返回类型不能是java.lang.Object,需要是更加具体的类型。通常这种情况下我们会定义一个抽象类来供所有的realData类型继承,这个类型只是一个标示作用,可以是空的,即没有任何属性定义在其中。这里为了简便起见,我们使用Dept和User的父类AbstractOrg来代替。调整后的ResultData定义如下:
public class ResultData {
private AbstractOrg realData;
public ResultData() {}
public ResultData(AbstractOrg realData) {
this.realData = realData;
}
public void setRealData(AbstractOrg realData) {
this.realData = realData;
}
@XmlElementRef
public AbstractOrg getRealData() {
return realData;
}
}
使用@XmlElementRef后整个节点的生成都将依赖于实际的子类型,包括根节点的名称,此时的根节点名称需要在子类型上通过@XmlRootElement来指定,而不再默认使用持有它的对象对应的属性的名称,即不再是默认的realData。
@XmlRootElement(name="dept")
public class Dept extends AbstractOrg {
private String no;
public Dept() {
super(AbstractOrg.TYPE_DEPT);
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
}
@XmlRootElement
public class User extends AbstractOrg {
private String username;
public User() {
super(AbstractOrg.TYPE_USER);
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
采用@XmlElementRef的方式时节点名称只能在子类上通过@XmlRootElement指定,不能再在对应的属性上通过@XmlElement来指定其它名称了,即上述的示例中不能在getRealData()方法上既有@XmlElementRef,又有@XmlElement,因为@XmlElementRef和@XmlElement是互斥的。
采用此种方式在创建JAXBContext时也需要传递本次进行XML和Java对象相互转换使用到的realData的实际类型,否则具体的实际类型将不被识别。如果期望JAXBContext时不传递realData的实际类型,也可以在JAXBContext能够识别的Class上通过@XmlSeeAlso来引入。通常是加在父类上的。
@XmlSeeAlso({Dept.class, User.class})
public abstract class AbstractOrg {
public static final int TYPE_DEPT = 1;
public static final int TYPE_USER = 2;
private Integer id;
private Integer parentId;
private String name;
/**
* 区分组织类型的,1是Dept,2是User
*/
private final int type;
protected AbstractOrg(int type) {
this.type = type;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getParentId() {
return parentId;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getType() {
return type;
}
}
这种方式也不是万能的。有的时候我们的Java类是有继承关系的,在生成XML时也期望可以按照子类的结构生成XML,也使用了@XmlElementRef,但是我们期望生成的XML的节点的名称都是相同的,比如上述示例如果期望无论是Dept还是User,对应的节点名称都是org。这时候如果我们有通过XML转换为Java对象的需求,那么XML的org节点将转换为Java的Dept对象还是User对象呢?实际上这时候JAXB将按照定义的顺序来,后面的将具有更高的优先级,但是这很明显不满足我们的需求。所以此种情形下我们要避免使用@XmlSeeAlso把所有的子类型都引入,具体的子类型还是在创建JAXBContext时老老实实的传入。
如果需要反过来通过XML生成Java对象也是可以的,JAXB也能正确的识别需要创建的对象。为了验证这个问题,笔者做了如下实验。把marshal的结果,即通过Java对象生成的XML保存起来,然后再通过unmarshal把对应的XML转换为Java对象,即Response对象,再通过Response.getData().getRealData()获取到转换后的realData,把转换后的realData和转换前的realData做比较。注意这时候不能用==,需要使用equals,因为它们肯定是两个不同的对象。
@Test
public void testMarshalUser() throws Exception {
User user = new User();
user.setId(1000);
user.setParentId(100);
user.setName("张三");
user.setUsername("zhangsan");
String xml = this.marshal(user);
Response response = JAXB.unmarshal(new StringReader(xml), Response.class);
AbstractOrg realData = response.getData().getRealData();
Assert.assertEquals(user, realData);
}
@Test
public void testMarshalDept() throws Exception {
Dept dept = new Dept();
dept.setId(100);
dept.setParentId(10);
dept.setName("财务部");
dept.setNo("A001");
String xml = this.marshal(dept);
Response response = JAXB.unmarshal(new StringReader(xml), Response.class);
AbstractOrg realData = response.getData().getRealData();
Assert.assertEquals(dept, realData);
}
private String marshal(AbstractOrg realData) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(Response.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Response response = new Response();
response.setErrorCode("0");
response.setErrorMessage("成功");
ResultData data = new ResultData(realData);
response.setData(data);
StringWriter writer = new StringWriter();
marshaller.marshal(response, writer);
return writer.toString();
}
为了使上述的测试结果能够顺利通过,还必须重写对应的Dept和User的equals方法,还需要在父类AbstractOrg上定义一个类似于equals那样比较所有属性值的方法以供子类在进行equals比较时调用。使用@XmlElementRef方式的并验证Java对象和XML能够完美相互转换的完整配置代码如下:
/**
* 根节点
*/
@XmlRootElement
@XmlType(propOrder= {"errorCode", "errorMessage", "data"})
public class Response {
private String errorCode;
private String errorMessage;
private ResultData data;
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public ResultData getData() {
return data;
}
public void setData(ResultData data) {
this.data = data;
}
}
/**
* data节点包装对象的包装类
*/
public class ResultData {
private AbstractOrg realData;
public ResultData() {}
public ResultData(AbstractOrg realData) {
this.realData = realData;
}
public void setRealData(AbstractOrg realData) {
this.realData = realData;
}
@XmlElementRef
public AbstractOrg getRealData() {
return realData;
}
}
/**
* 抽象的组织机构,提取出公用的id、父级id和名称
*/
@XmlSeeAlso({Dept.class, User.class})
public abstract class AbstractOrg {
public static final int TYPE_DEPT = 1;
public static final int TYPE_USER = 2;
private Integer id;
private Integer parentId;
private String name;
/**
* 区分组织类型的,1是Dept,2是User
*/
private final int type;
protected AbstractOrg(int type) {
this.type = type;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getParentId() {
return parentId;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getType() {
return type;
}
protected boolean superEquals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractOrg other = (AbstractOrg) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (parentId == null) {
if (other.parentId != null)
return false;
} else if (!parentId.equals(other.parentId))
return false;
if (type != other.type)
return false;
return true;
}
}
@XmlRootElement(name="dept")
public class Dept extends AbstractOrg {
private String no;
public Dept() {
super(AbstractOrg.TYPE_DEPT);
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Dept other = (Dept) obj;
if (no == null) {
if (other.no != null)
return false;
} else if (!no.equals(other.no))
return false;
if (!this.superEquals(obj)) {//父结构不能equals
return false;
}
return true;
}
}
@XmlRootElement
public class User extends AbstractOrg {
private String username;
public User() {
super(AbstractOrg.TYPE_USER);
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
if (!this.superEquals(obj)) {//父结构不能equals
return false;
}
return true;
}
}
以上就是以子类结构绑定XML的两种方式。
相关推荐
使用jaxb生成XML例子,含有例子和注解解析
用JAXB生成一个XML文档,一个XML 模式(Schema)用XML语法表达了一个XML文档的结构。J2EE的开发者也许会需要一个符合XML模式的XML文档。Java XML绑定架构(JAXB)提供了一个绑定编译器,xjc,来从一个XML模式中生成Java类...
JAXB注解 java 关于xml的注解,自动生成xml文件
使用jaxb根据xsd生成xml文档,不要积分的,赶快下载
jaxb解析生成xml例子
JAXB工具类 xml转为java对象 java对象转为xml ,本人亲测,可以使用!!!
针对 xml 定义 生成实体类工具 无密码 解压后使用
java 使用 JAXB 将xml转换为 bean 包含xml和dto和读取文件的util类
编写xsd文件,利用jaxb生成java类。
使用jaxb 实现xml——bean互转
jaxb xml 转map
NULL 博文链接:https://luyuwww.iteye.com/blog/1988355
jaxb 将xml里面的对象转化为一个个类,大大地简化了xml的相关操作。unmarshal marshal
教你使用jaxb解析xml,介绍了主要的注解,以及核心api
JAXB(Java Architecture for XML Binding) 是一个业界的标准,是一项可以根据XML Schema产生Java类的技术。JAXB与xml相互转换实例。
JAXB教程 JAXB JAXB插件 里面有很详细的使用说明,看了就知道,JAXB解析XML真的很好用
在android 6.0下,应用JAXB jar包根据XML Schema解析XML文件。
XML Schema 描述 XML 文档的结构。 XML Schema 语言也称作 XML Schema 定义(XML Schema Definition,XSD)。 在此教程中,你将学习如何在应用程序中读取和创建 XML Schema 语言,XML Schema 为何比 DTD 更加强 大,...
使用woodstax+jaxb进行xml的流解析,包括解析类,解析文件,所需jar包,带注解的实体类。提高了解析效率,减少了内存消耗。
JAXB XML TO JAVA,文件转化 生成java代码