Elim的博客

用来记录一些原创性的总结


Elim

JAXB系列之通过XmlJavaTypeAdapter自动加上CDATA

使用JAXB处理对象与XML进行绑定时,可能我们对象中的某个属性的值可能包含标签。虽然字符串在转换为XML的文本节点时会自动的对其中的标签进行转义,但是我们也可以对它进行不转译处理。对于接收方来讲,如果节点包含的内容是需要当做一段纯文本处理的,那么就应该采用<![CDATA[...]]>形式把它包起来,不然就会被误会成当一个子节点来处理。先来看一个示例。

假设我们有一段XML文本需要转换为一个Java对象,Java对象的定义如下:

	@XmlRootElement
	public static class Response {
		private int code;
		private String message;
		private String returnValue;
		
		public int getCode() {
			return code;
		}
		public void setCode(int code) {
			this.code = code;
		}
		public String getMessage() {
			return message;
		}
		public void setMessage(String message) {
			this.message = message;
		}
		public String getReturnValue() {
			return returnValue;
		}
		public void setReturnValue(String returnValue) {
			this.returnValue = returnValue;
		}
		@Override
		public String toString() {
			return "Response [code=" + code + ", message=" + message + ", returnValue=" + returnValue + "]";
		}
		
	}

其中的returnValue是一段特殊的XML格式的文本,现假设我们的returnValue的值对应的XML元素的文本节点值是一段XML,比如<a>1</a>,那么我们期望它是可以被赋值给returnValue属性的。我们进行如下测试:

 	@Test
	public void test() {
		String xml = "<response><code>200</code><message>Success</message><returnValue><a>1</a></returnValue></response>";
		Response response = JAXB.unmarshal(new StringReader(xml), Response.class);
		System.out.println(response);
		
	}

结果输出如下:

 Response [code=200, message=Success, returnValue=]

很明显,returnValue的值是一个空字符串,而不是我们期望的<a>1</a>。这是因为<a>1</a>被当做了returnValue的一个子标签。这就相当于returnValue是一个复杂对象,但实际上它就是一个String类型,JAXB在转换的时候会先调用String的无参构造方法创建一个String对象,然后解析到其子标签<a>1</a>时会把它当做String的一个属性,尝试获取其set方法,但是该方法不存在,所以最终创建的就是一个空字符串(String的无参构造方法创建的是一个空字符串)。那如果我们期望它可以正确的把<a>1</a>赋值给returnValue应该怎么办呢?只需要把<a>1</a><![CDATA]]>包起来即可,这样JAXB在转换时就知道它对应的是一段纯文本,且会自动去掉<![CDATA]]>

	@Test
	public void test() {
		String xml = "<response><code>200</code><message>Success</message><returnValue><![CDATA[<a>1</a>]]></returnValue></response>";
		Response response = JAXB.unmarshal(new StringReader(xml), Response.class);
		System.out.println(response);
		
	}

<a>1</a><![CDATA[]]>包起来后,转换的效果如下:

Response [code=200, message=Success, returnValue=<a>1</a>]

returnValue已经被成功赋值了。进行unmarshal时我们需要包含标签的纯文本能够用<![CDATA[]]>包起来,那就需要生产XML的一方能够在进行marshal的时候添加。这种重复逻辑如果在每个地方都得手动的加一遍会比较麻烦。我们可以通过定义一个XmlJavaTypeAdapter来实现自动的添加。为此我们需要定义一个可以自动添加<![CDATA[]]>的XmlAdapter实现类,在实现其marshal方法时需要考虑到传递进来的值为null的场景。

	public static class CDATAAdapter extends XmlAdapter<String, String> {

		@Override
		public String unmarshal(String v) throws Exception {
			return v;
		}

		@Override
		public String marshal(String v) throws Exception {
			if (v != null) {
				return "<![CDATA[" + v + "]]>";
			}
			return null;
		}
		
	}

然后在我们的Response的returnValue属性的get方法上加上@XmlJavaTypeAdapter注解,并指定需要使用的XmlAdapter为上面实现的CDATAAdapter。

	@XmlRootElement
	public static class Response {
		private int code;
		private String message;
		private String returnValue;
		
		public int getCode() {
			return code;
		}
		public void setCode(int code) {
			this.code = code;
		}
		public String getMessage() {
			return message;
		}
		public void setMessage(String message) {
			this.message = message;
		}
		@XmlJavaTypeAdapter(CDATAAdapter.class)
		public String getReturnValue() {
			return returnValue;
		}
		public void setReturnValue(String returnValue) {
			this.returnValue = returnValue;
		}
		@Override
		public String toString() {
			return "Response [code=" + code + ", message=" + message + ", returnValue=" + returnValue + "]";
		}
		
	}

这样我们在对Response类型的对象进行marshal时就会自动为returnValue加上<![CDATA[]]>了,测试用例如下:

	@Test
	public void testMarshal() {
		Response response = new Response();
		response.setCode(200);
		response.setMessage("success");
		response.setReturnValue("<a><b>123</b></a>");
		JAXB.marshal(response, System.out);
	}

测试的输出结果如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<response>
    <code>200</code>
    <message>success</message>
    <returnValue>&lt;![CDATA[&lt;a&gt;&lt;b&gt;123&lt;/b&gt;&lt;/a&gt;]]&gt;</returnValue>
</response>

我们可以看到我们给returnValue赋值的<a><b>123</b></a>被自动的加上了<![CDATA[]]>,但是其中的标签都自动被转译了。这是JAXB的默认行为。这将在后文介绍如何让JAXB不自动对包含标签的XML文本节点进行自动转译。

(本文由Elim写于2017年7月29日)