处理动态元素或属性
动态元素
假设现在有一项解析XML的需求,该XML的大体结构是固定的,如下这样:
<response>
<errorCode></errorCode>
<errorMessage></errorMessage>
<data>
</data>
</response>
但是data节点下包含的子节点的内容是不固定的,比如它可能是这样:
<response>
<errorCode></errorCode>
<errorMessage></errorMessage>
<data>
<result1>
<result11></result11>
<result12></result12>
</result1>
</data>
</response>
也可能是下面这样:
<response>
<errorCode></errorCode>
<errorMessage></errorMessage>
<data>
<result2>
<result21></result21>
<result22></result22>
</result2>
</data>
</response>
还可能是下面这样:
<response>
<errorCode></errorCode>
<errorMessage></errorMessage>
<data>
<d1>
<d11></d11>
<d12></d12>
</d1>
<d2>
<d21></d21>
<d22></d22>
</d2>
</data>
</response>
实际情况可能比这个更复杂,笔者这里就简单的考虑上面几种情况,但是它不会影响我们对动态节点的解释。上面的XML的整体结构是固定的,但是data下面的节点内容是动态的,可能也是不可预料的。基于上面这样的需求你可能想我们把result1、result2、d1和d2都定义在data节点对应的Java类下面即可,然后在不同的XML时对应的result1或者result2或者d1、d2属性有值。这个时候对应的Java类定义可能是下面这样:
@XmlRootElement
public class Response {
private String errorCode;
private String errorMessage;
private ResultData data;
//...省略get和set方法
}
public class ResultData {
private Result1 result1;
private Result2 result2;
private D1 d1;
private D2 d2;
//...省略get和set方法
}
public class Result1 {
private String result11;
private String result12;
//...省略get和set方法
}
public class Result2 {
private String result21;
private String result22;
//...省略get和set方法
}
public class D1 {
private String d11;
private String d12;
//...省略get和set方法
}
public class D2 {
private String d21;
private String d22;
//...省略get和set方法
}
基于上面的XML这样的配置是可以的,但倘若data下面还有很多种可能性,可能有几百种甚至更多不同的结构,把它们都分别定义在ResultData里面也不太现实。这个时候就可以使用@XmlAnyElement来映射data节点下的内容了,@XmlAnyElement可以用来匹配任何不能够被精确匹配的元素,如果动态元素只会有一个则可以把需要动态映射的属性定义为Object或org.w3c.dom.Element
类型。如果有多个则需要把它定义为Object或org.w3c.dom.Element
对应的集合类型。所以对于上述需求我们可以把ResultData类定义如下:
public static class ResultData {
private List<Object> datas;
@XmlAnyElement
public List<Object> getDatas() {
return datas;
}
public void setDatas(List<Object> datas) {
this.datas = datas;
}
}
然后我们对下面这段XML转换为Java对象做一个测试:
<response>
<errorCode>0</errorCode>
<errorMessage>成功</errorMessage>
<data>
<d1>
<d11>A</d11>
<d12>B</d12>
</d1>
<d2 status="1">
<d21>D</d21>
<d22>E</d22>
</d2>
</data>
</response>
测试代码如下:
@Test
public void testDynamic() throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(Response.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
InputStream xml = this.getClass().getClassLoader().getResourceAsStream("jaxb/dynamic.xml");
Response response = (Response) unmarshaller.unmarshal(xml);
List<Object> datas = response.getData().getDatas();
Assert.assertEquals(datas.size(), 2);
Assert.assertTrue(datas.get(0) instanceof Element);
Assert.assertTrue(datas.get(1) instanceof Element);
}
上面的测试代码是可以完全通过的,所以你没有看错,转换为对象后ResultData.getDatas()中包含两个元素,且这两个元素都是org.w3c.dom.Element
类型,因为这个时候对于JAXB来说是不识别d1和d2对应的对象的,所以只能把它们转换为org.w3c.dom.Element
类型,其实这在某些比较复杂的情况下,当某一动态节点确实不好映射为一些具体的对象时,退一步处理Element也是一种很好的方式。如果需要能转换为对应的对象,我们需要指定XmlAnyElement的lax属性为true。
public class ResultData {
private List<Object> datas;
@XmlAnyElement(lax=true)
public List<Object> getDatas() {
return datas;
}
public void setDatas(List<Object> datas) {
this.datas = datas;
}
}
D1和D2的类上需要加上@XmlRootElement,让节点与希望被转换的Java类相匹配,如果节点名称与Java类的名称不一致,可以通过@XmlRootElement的name属性指定需要匹配的节点的名称。
@XmlRootElement
public class D1 {
private String d11;
private String d12;
public String getD11() {
return d11;
}
public void setD11(String d11) {
this.d11 = d11;
}
public String getD12() {
return d12;
}
public void setD12(String d12) {
this.d12 = d12;
}
}
@XmlRootElement
public class D2 {
private String d21;
private String d22;
public String getD21() {
return d21;
}
public void setD21(String d21) {
this.d21 = d21;
}
public String getD22() {
return d22;
}
public void setD22(String d22) {
this.d22 = d22;
}
}
与此同时,在创建JAXBContext时需要加入对应的Class,改造后的单元测试代码如下,测试也是可以通过的。这说明d1节点确实被转换为D1类型的对象了,d2节点也被转换为D2类型的对象了。
@Test
public void testDynamic() throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(Response.class, D1.class, D2.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
InputStream xml = this.getClass().getClassLoader().getResourceAsStream("jaxb/dynamic.xml");
Response response = (Response) unmarshaller.unmarshal(xml);
List<Object> datas = response.getData().getDatas();
Assert.assertEquals(datas.size(), 2);
Assert.assertTrue(datas.get(0) instanceof D1);
Assert.assertTrue(datas.get(1) instanceof D2);
}
基于这样的配置,把对象转换为XML也是可以的,这里简单的拿D1和D2示例如下。
@Test
public void testDynamic() throws Exception {
Response response = new Response();
response.setErrorCode("0");
response.setErrorMessage("成功");
ResultData data = new ResultData();
List<Object> datas = new ArrayList<>();
D1 d1 = new D1();
d1.setD11("A");
d1.setD12("B");
datas.add(d1);
D2 d2 = new D2();
d2.setD21("D");
d2.setD22("E");
datas.add(d2);
data.setDatas(datas);
response.setData(data);
JAXBContext jaxbContext = JAXBContext.newInstance(Response.class, D1.class, D2.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(response, System.out);
}
生成的XML如下,其中的节点顺序是因为我们没有指定所以data跑前面去了,但是这不影响:
<response>
<data>
<d1>
<d11>A</d11>
<d12>B</d12>
</d1>
<d2>
<d21>D</d21>
<d22>E</d22>
</d2>
</data>
<errorCode>0</errorCode>
<errorMessage>成功</errorMessage>
</response>
有兴趣的读者可以试验开始介绍的Result1、Result2,或者是更复杂的,更动态的场景。
动态属性
考虑这样一种场景,你有一个元素上面会有很多不同的属性,这些属性是动态的,你不能完全预知到其中会包含哪些属性,或者属性太多了你不能一一把它们列举出来与对象的属性一一对应。比较典型的示例就是HTML元素,它除了标准的属性之外,有时为了程序的需要我们可能会指定一些自定义的属性,这些属性还不固定。这样的情景我们就可以通过一个Map来接收这些动态的属性,然后在上面使用@XmlAnyAttribute,这样在转换为对象时JAXB就会把那些不能精确匹配到对应的属性上的XML元素属性映射到对应的Map上。假设有类似下面这样一段XML需要应用动态的属性匹配。
<holder prop1="value1" prop2="value2" prop3="value3"/>
可以建立类似下面这样的代码进行匹配,需要注意的是Map对应的Key需要是javax.xml.namespace.QName
类型的。
@XmlRootElement(name="holder")
public class AnyAttributeHolder {
private Map<QName, String> attrs;
@XmlAnyAttribute
public Map<QName, String> getAttrs() {
return attrs;
}
public void setAttrs(Map<QName, String> attrs) {
this.attrs = attrs;
}
}
可以进行验证如下:
@Test
public void test2() {
String xml = "<holder prop1=\"value1\" prop2=\"value2\" prop3=\"value3\"/>";
AnyAttributeHolder holder = JAXB.unmarshal(new StringReader(xml), AnyAttributeHolder.class);
Assert.assertTrue(holder.getAttrs().get(new QName("prop1")).equals("value1"));
Assert.assertTrue(holder.getAttrs().get(new QName("prop2")).equals("value2"));
Assert.assertTrue(holder.getAttrs().get(new QName("prop3")).equals("value3"));
}
如果你还有把它marshal为XML的需要,也是可以进行的,如:
@Test
public void test() {
Map<QName, String> attrs = new HashMap<>();
for (int i=0; i<5; i++) {
attrs.put(new QName("prop" + (i+1)), "value" + (i+1));
}
AnyAttributeHolder holder = new AnyAttributeHolder();
holder.setAttrs(attrs);
StringWriter writer = new StringWriter();
JAXB.marshal(holder, writer);
System.out.println(writer);
}
生成的XML是如下这样:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<holder prop2="value2" prop1="value1" prop5="value5" prop4="value4" prop3="value3"/>
这就是JAXB对于动态属性的处理方式。
相关推荐
JAXB向Xml非根节点添加一个或多个属性 java转xml字符串
处理cxf版本和jaxb包中冲突问题,解决webservice中遇到的版本问题
使用jaxb 实现xml——bean互转
JAXB的使用JAXB的使用JAXB的使用JAXB的使用
(一个复杂类型通过指定属性和元素内的元素来提供对一个元素的限定)。一个符合XML模式的XML文档可以从这些Java类中构建出来。 在这篇教程中,作者使用了JAXB用来从一个XML模式中生成Java类。这些Java类将会生成一...
无需安装,解压后即可。jaxb-api.jar,jaxb-xjc.jar,jaxb-impl.jar,activation.jar等相关jar包在lib文件夹下。
jaxb-api jaxb-impl jar
JAXB的 eclipse插件 JAXB的 eclipse插件 JAXB的 eclipse插件 JAXB的 eclipse插件 JAXB的 eclipse插件
JAXB的安装包及插件
在使用webservice,mule esb等需要jaxb的项目里经常会出现 JAXB 2.0 API is being loaded from the bootstrap classloader这个错误,按照打出的信息Use the endorsed directory mechanism to place jaxb-api.jar in ...
java -jar JAXB2_20060607.jar On Windows, you can just double-click the jar file to execute. Release Notes Browse the release notes online, including what's new. Technical Support Please subscribe to...
JAXB注解相关技术JAXB注解相关技术JAXB注解相关技术JAXB注解相关技术JAXB注解相关技术JAXB注解相关技术
jaxb2.0文档,java于xml动态绑定框架,很好的东西。
使用jaxb生成XML例子,含有例子和注解解析
有关Maven项目中缺少jaxb-api的异常报错解决,jaxb-api-2.3.0.jar
解释并解决java开发中使用jaxb解析或组装xml遇到乱码的问题。
一个JAXB2的基础教程,教你快速运用JAXB通过XSD绑定XML和读写XML
jaxb-2.2.jar jaxb jax
jaxb-api jaxb-impl jaxb-xjc jaxws-rt 这四个文件对应的jar包
现在很多maven仓库均没有此jar jaxb-api-2.3.0.jar jaxb-core-2.3.0.jar