Elim的博客

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


Elim

JAXB系列之Map转XML之方法一

假设我们需要生成如下这样一段XML代码,condition元素下是若干个包含一个文本节点的元素,这样的元素个数不定。如果需要把它们定义为一个Java类,很明显应该定义为Map结构比较合适。

<request>
    <condition>
        <key_0>value_0</key_0>
        <key_1>value_1</key_1>
        <key_2>value_2</key_2>
        <key_3>value_3</key_3>
        <key_4>value_4</key_4>
    </condition>
</request>

为此,我们很容易想到的Java类结构是如下这样的。

	@XmlRootElement
	@XmlAccessorType(XmlAccessType.FIELD)
	public static class Request {
		
		@XmlElement
		private Map<String, String> condition;

	}

然后我们来做个测试,看是否可以达到我们想要的效果,测试代码如下:

	@Test
	public void test() throws Exception {
		Request request = new Request();
		Map<String, String> condition = new LinkedHashMap<>();
		for (int i=0; i<5; i++) {
			condition.put("key_" + i, "value_" + i);
		}
		request.condition = condition;
		
		JAXBContext jaxbContext = JAXBContext.newInstance(Request.class);
		Marshaller marshaller = jaxbContext.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		marshaller.marshal(request, System.out);
	}

上面的测试代码输出如下:

<request>
    <condition>
        <entry>
            <key>key_0</key>
            <value>value_0</value>
        </entry>
        <entry>
            <key>key_1</key>
            <value>value_1</value>
        </entry>
        <entry>
            <key>key_2</key>
            <value>value_2</value>
        </entry>
        <entry>
            <key>key_3</key>
            <value>value_3</value>
        </entry>
        <entry>
            <key>key_4</key>
            <value>value_4</value>
        </entry>
    </condition>
</request>

没有达到我们想要的效果。不像集合类型那样,JAXB没有对Map进行特殊的处理,它纯粹把它当做一个普通的Java对象来解析的,所以出来的效果就是上面那样的。既然普通的Map会按照Java对象来解析,那么我们如果想要Map结构按照预想的那样转换为XML,那么我们是否可以构造出类似的结构呢?按照这种想法,笔者的思路是把我们的Map包装起来,应用XmlElementWrapper的特殊处理,自定义一个继承自ArrayList的类,把每一个key/value都当做是ArrayList中的一个元素。由于这个元素在展示为XML时需要获取动态的元素名(取key的名称),为此,把它封装为一个JAXBElement对象。为此笔者自己创建的简单的具有类似Map功能的XmlMap结构如下:

	public static class XmlMap extends ArrayList<JAXBElement<String>> {

		/**
		 * 
		 */
		private static final long serialVersionUID = -7047805724967522158L;
		
		private Map<String, JAXBElement<String>> keyMap = new HashMap<>();
		private static ObjectFactory objectFactory = new ObjectFactory();
		
		public void put(String key, String value) {
			if (keyMap.containsKey(key)) {
				super.remove(keyMap.get(key));
			}
			JAXBElement<String> ele = objectFactory.createXmlMap(key, value);
			super.add(ele);
			keyMap.put(key, ele);
		}
		
	}

对应的ObjectFactory类定义如下:

	@XmlRegistry
	public static class ObjectFactory {
		
		@XmlElementDecl(name="xmlMap")
		public JAXBElement<String> createXmlMap(String key, String value) {
			QName name = new QName(key);
			JAXBElement<String> ele = new JAXBElement<>(name, String.class, value);
			return ele;
		}
		
	}

ObjectFactory中的createXmlMap把一个key/value对封装为了一个JAXBElement对象,且对应的XML元素名称是取的key的值。然后把我们的Request中的Map替换为我们自定义的XmlMap。

	@XmlRootElement
	@XmlAccessorType(XmlAccessType.FIELD)
	public static class Request {
		
		@XmlElementWrapper(name="condition")
		@XmlElementRef(name="xmlMap")
		private XmlMap condition;

	}

测试类里面也只是把Map替换为XmlMap。

	@Test
	public void test() throws Exception {
		Request request = new Request();
		XmlMap condition = new XmlMap();
		for (int i=0; i<5; i++) {
			condition.put("key_" + i, "value_" + i);
		}
		request.condition = condition;
		
		JAXBContext jaxbContext = JAXBContext.newInstance(Request.class, ObjectFactory.class);
		Marshaller marshaller = jaxbContext.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		marshaller.marshal(request, System.out);
	}

然后运行上面的测试用例,出来的效果如下:

<request>
    <condition>
        <key_0>value_0</key_0>
        <key_1>value_1</key_1>
        <key_2>value_2</key_2>
        <key_3>value_3</key_3>
        <key_4>value_4</key_4>
    </condition>
</request>

跟我们一开始期望的一样。笔者示例中的XmlMap只有简单的类似于java.util.Map的存储单个元素的功能,如需要更多的类似Map的功能,则可以做进一步的封装。
接下来,在下一篇博文中笔者还会介绍另一种把Map转换为XML的方法,其在Request中定义的将是一个真正的java.util.Map对象。

(注:本文由Elim写于2017年8月9日)