用来记录一些原创性的总结
假设现在有这样一项工作,要求你写两个接口出来,它们对外提供的数据是XML格式,分别对应如下格式。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<response>
<message>AAAA</message>
<data>
<dept id="3">
<name>技术部</name>
<no>003001</no>
</dept>
</data>
<returnCode>100</returnCode>
</response>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<response>
<message>AAAA</message>
<data>
<person id="1">
<name>张三</name>
</person>
</data>
<returnCode>100</returnCode>
</response>
经过分析我们可以发现这两个接口的返回格式是类似的,不同的是resultData
元素包裹的内容是不一样的。为此,如果我们使用JAXB
进行XML
的格式化时我们就会想到定义好response
元素对应的类,抽象出其中的data
节点下面的元素。为此你可能会定义出如下这些类。
@XmlRootElement
class Response {
private int returnCode;
private String message;
private ResultData resultData;
public int getReturnCode() {
return returnCode;
}
public void setReturnCode(int returnCode) {
this.returnCode = returnCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@XmlElement(name = "data")
public ResultData getResultData() {
return resultData;
}
public void setResultData(ResultData resultData) {
this.resultData = resultData;
}
}
class ResultData {
private Data realData;
public Data getRealData() {
return realData;
}
public void setRealData(Data realData) {
this.realData = realData;
}
}
class Data {
}
class Person extends Data {
private Integer id;
private String name;
@XmlAttribute
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@XmlRootElement
class Dept extends Data {
private Integer id;
private String no;
private String name;
@XmlAttribute
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后进行如下这样的测试。
public class XmlElementRefTest {
@Test
public void testPerson() throws Exception {
Person person = new Person();
person.setId(1);
person.setName("张三");
Response response = this.newResponse(person);
this.marshal(response, Response.class, Person.class);
}
private void marshal(Response response, Class<?>... classesToBeBound) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(classesToBeBound);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(response, System.out);
}
private Response newResponse(Data realData) {
Response response = new Response();
response.setReturnCode(100);
response.setMessage("AAAA");
ResultData resultData = new ResultData();
resultData.setRealData(realData);
response.setResultData(resultData);
return response;
}
}
然而,很不幸的是你会得到如下这样的结果。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<response>
<message>AAAA</message>
<data>
<realData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="person" id="1">
<name>张三</name>
</realData>
</data>
<returnCode>100</returnCode>
</response>
从上面的结果我们可以看到,生成XML
时确实用的是子类对象Person
,但是对应的节点名称还是默认的属性名称,即realData
。那有什么办法可以使元素名称由realData
变为person
或dept
,同时又去掉那些xmlns:xsi
等属性呢?这时候就可以用到XmlElementRef
注解了,把它标注在ResultData
的getRealData()
方法上,这就告诉JAXB
,在进行XML
转换时不要以realData
属性的声明类型Data
进行转换,而是以传递进来的真实类型进行转换。使用了XmlElementRef
标注后,对应的属性生成的XML
元素名称已经不再由当前属性来确定了,将由实际传入类型上的XmlRootElement
指定的名称决定。所以我们还需要在Person
类上使用XmlRootElement
注解进行标注,可以通过其name
属性来指定生成的XML
元素的名称,不指定时默认将使用类名称,首字母小写。如果忘记在Person
类上加上XmlRootElement
注解了,那么你会遇到如下这样的错误。
com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
@XmlElementRef 无效: 类型 "class xxx.Data" 或其任意子类对此上下文是未知的。
调整后我们的ResultData
和Person
类定义变成如下这样。
class ResultData {
private Data realData;
@XmlElementRef
public Data getRealData() {
return realData;
}
public void setRealData(Data realData) {
this.realData = realData;
}
}
@XmlRootElement
class Person extends Data {
private Integer id;
private String name;
@XmlAttribute
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
继续运行我们的测试类,你将得到如下这样的输出。这时候输出结果就跟我们预想的一样了。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<response>
<message>AAAA</message>
<data>
<person id="1">
<name>张三</name>
</person>
</data>
<returnCode>100</returnCode>
</response>
同理,对于Dept
类而言,我们也可以在其上面使用XmlElementRef
注解进行标注。
需要注意的是:在进行
marshal
时,上下文类环境中一定要加上当前的子类型,即进行Person
类转换时就加上Person.class
,进行Dept
类转换时就加上Dept.class
。它们对应于测试代码中的classesToBeBound
。如果没有指定则将出现异常。
以下是正常的、完整的示例代码。
public class XmlElementRefTest {
@Test
public void testPerson() throws Exception {
Person person = new Person();
person.setId(1);
person.setName("张三");
Response response = this.newResponse(person);
this.marshal(response, Response.class, Person.class);
}
@Test
public void testDept() throws Exception {
Dept dept = new Dept();
dept.setId(3);
dept.setNo("003001");
dept.setName("技术部");
Response response = this.newResponse(dept);
this.marshal(response, Response.class, Dept.class);
}
private void marshal(Response response, Class<?>... classesToBeBound) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(classesToBeBound);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(response, System.out);
}
private Response newResponse(Data realData) {
Response response = new Response();
response.setReturnCode(100);
response.setMessage("AAAA");
ResultData resultData = new ResultData();
resultData.setRealData(realData);
response.setResultData(resultData);
return response;
}
}
@XmlRootElement
class Response {
private int returnCode;
private String message;
private ResultData resultData;
public int getReturnCode() {
return returnCode;
}
public void setReturnCode(int returnCode) {
this.returnCode = returnCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@XmlElement(name = "data")
public ResultData getResultData() {
return resultData;
}
public void setResultData(ResultData resultData) {
this.resultData = resultData;
}
}
class ResultData {
private Data realData;
@XmlElementRef
public Data getRealData() {
return realData;
}
public void setRealData(Data realData) {
this.realData = realData;
}
}
class Data {
}
@XmlRootElement
class Person extends Data {
private Integer id;
private String name;
@XmlAttribute
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@XmlRootElement
class Dept extends Data {
private Integer id;
private String no;
private String name;
@XmlAttribute
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
(注:本文由Elim写于2017年6月19日)