`
liugang594
  • 浏览: 987507 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

控制JAXB的输入输出

 
阅读更多

上一节介绍了如何在解析模型的时候构建模型之间的父子链,其实使用afterUnmarshal()或beforeUnmarshal()方法或Unmarshaller.Listener都可以用来参与到模型的解析过程,也就是输入过程。关于输入过程的参与没有过多的说明,这节主要介绍输出的参与。

 

一般情况下,所有声明的jaxb的属性和元素都会事无巨细的被保存到xml的文件中,例如还是使用上例中Students的例子,可能保存的文件内容如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:students xmlns:ns2="http://www.liulutu.com/students/">
	<student sex="Female" name="Bob" birthday="2013-11-27+08:00" />
</ns2:students>

 假设,默认的值就是Female,那就可以省略sex的设置,期望的内容就变成了:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:students xmlns:ns2="http://www.liulutu.com/students/">
	<student name="Bob" birthday="2013-11-27+08:00" />
</ns2:students>

在继续之前,有一点需要说明一下就是:如果想某项内容不输入,则可以通过设置该项内容为空来实现。所以要想让Female不输入,则需要想办法让它变成空。

 

方法一:使用public void beforeMarshal(Marshaller m)方法

可以在StudentType里定义public void beforeMarshal(Marshaller m)方法,其中方法内容为:

	public void beforeMarshal(Marshaller m) {
		if (SexType.FEMALE == this.sex) {
			this.sex = null;
		}
	}

这样修改的一个潜在问题就是在这之后再读取sex变量,值可能就是空的,因为也需要修改getSex()方法,让它在null值的情况下返回一个默认值:

	public SexType getSex() {
		return sex==null?SexType.FEMALE:sex;
	}

或者引入一个中间变量,就是不直接存储sex (把它标记成,而是引入一个dummySex,这个对象外界修改不了,只在保存的时候才有用,例如: 

	@XmlAttribute(name = "sex")
	protected SexType dummySex = null;

	@XmlTransient
	protected SexType sex;

 

	public void beforeMarshal(Marshaller m) {
		if (SexType.FEMALE != this.sex) {
			this.dummySex = this.sex;
		}
	}

不过这样一来,就需要在解析的时候做dummySex到sex的映射(使用unmarshal()方法可以做到),和上面相比,没什么改进。

根据上面的说明:只有值是空的时候才不保存,那对于像int,float之类的primitive类型就有问题了,因为他们没有办法设置为null,一个可能的变通方法就是将这些primitive对象类型定义为他们的wrap类,例如Integer,Float等,然后就可以解决这个问题了。

 

方法二:使用Marshaller.Listener

同方法一类型,只是把各个模型中定义beforeMarshal()方法变成定义在监听事件里。

 

方法三:使用XmlAdapter

上面介绍的方法都有一定的缺点:要么要把内容设置为空;要么要引入某种中介变量。实际上,在保存之后,所有的内容都可以认为是一个普通的字符串,所以可以想办法把所有的内容转变成某个符串再输入就可以了,然后根据需求,决定是转成一个有意义的字符串还是一个null字符,如果是null字符串,则结果就不会被保存。

 

我们可以使用XmlAdapter来实际从某个字符到某个类型的转换,如上描述,最简单的就是所有的内容都转换成或null的字符串,或类型对应的字符串内容。例如从上面的SexType转成字符串的XmlAdapter的实现:

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class SexTypeXmlAdapter extends XmlAdapter<String, SexType> {

	@Override
	public String marshal(SexType v) throws Exception {
		if (v == null || v == SexType.FEMALE) {
			return null;
		}
		return v.name();
	}

	@Override
	public SexType unmarshal(String v) throws Exception {
		if (v == null) {
			return SexType.FEMALE;
		}
		return SexType.valueOf(v);
	}

}

同样的,只有在不是FEMALE的时候才保存。不过这里用的是XmlAdapter来实现的,所以再也不需要beforeMarshal()方法,也不需要引入中介变量。不过我需要告诉JAXB,说在sex变量上给我应用这个转换,这个通过在sex变量上添加以下声明实现:

	@XmlAttribute(name = "sex")
	@XmlJavaTypeAdapter(SexTypeXmlAdapter.class)
	protected SexType sex;

这样就实现我们的意图。

 

关于XmlAdapter的更多内容:

看看XmlJavaTypeAdapter的声明:

@Retention(RUNTIME) @Target({PACKAGE,FIELD,METHOD,TYPE,PARAMETER})
public @interface XmlJavaTypeAdapter

可以看出这个annotation可以用在从包级到参数级的所有级别上,这样的一个好外就是:假设我的类中或者包中有很多这样SexType类型,那要一个一个添加就相当麻烦了,这个时候就可以所这个声明加在更高一级的对象上,例如加在类级别上,则所有该类中的此类对象都自动应用。

 

@XmlJavaTypeAdapters

除此之外,还有另一个annotation XmlJavaTypeAdapters,从名字上可以看出它是XmlJavaTypeAdapter的复数形式,就是说可以包含多个XmlJavaTypeAdapter声明:

@Retention(RUNTIME) @Target({PACKAGE})
public @interface XmlJavaTypeAdapters 

从声明中可以看出,这个只能用在包级别上,例如:

@XmlJavaTypeAdapters({@XmlJavaTypeAdapter(ItemTypeValueAdapter.class),@XmlJavaTypeAdapter(BooleanValueAdapter.class), @XmlJavaTypeAdapter(StringValueAdapter.class), @XmlJavaTypeAdapter(IntValueAdapter.class)})
package cn.com.bjfanuc.assessment.models;

import javax.xml.bind.annotation.adapters.*;

不过这里有一个问题:怎么在包级别上使用这些annotation呢?这就涉及到package-info.java文件了。 

pacakge-info.java

这个文件是一个特殊的java文件,通常用来声明一些和包相关的信息,不能含有公共或私有类声明,关于它的介绍,可以参考 http://strong-life-126-com.iteye.com/blog/806246 .

上面的XmlJavaTypeAdapters就需要定义在这样的一个文件里。其实包名和引用的依赖需要声明。

 

CDATA

如果想把某个字段保存成cdata类型,我没发现什么好方法,好像总是需要做某种特殊处理,否则< 或 >会被转义。

例如,我们想name保存成以下格式:

<name><![CDATA[ bob ]]></name>

首先修改一下name的annotation,从attribute改成element

	@XmlElement(name="name")
	protected String name;

然后要做的就怎么让它变成一个CDATASection,方法之一就是如上面所示,用XmlAdapter来做,如下:

public class CDATASectionAdapter 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;
	}

}

然后在name上使用这个adapter

	@XmlElement(name="name")
	@XmlJavaTypeAdapter(CDATASectionAdapter.class)
	protected String name;

这个时候,输入的内容大致:

<name>&lt;![CDATA[Bob]]&gt;</name>

可以看到,最前的<和最后的>都被转义了。因此我们需要告诉Marshaller,碰到cdata的时候不要转义。可以通过给Marshaller设置定义的CharacterEscapeHandler属性来实现。

首先,看看我们的自定义气CharacterEscapeHandler类:

class CustomCharacterEscapeHandler implements CharacterEscapeHandler {
	private int cdataMiniLength = "<![CDATA[]]>".length();
	private static String[] escapeList = new String[63];
	static {
		escapeList[(int) '&'] = "&amp;";
		escapeList[(int) '<'] = "&lt;";
		escapeList[(int) '>'] = "&gt;";
		escapeList[(int) '"'] = "&quot;";
		escapeList[(int) '\t'] = "&#x9;";
		escapeList[(int) '\r'] = "&#xD;";
		escapeList[(int) '\n'] = "&#xA;";
	}


	public void escape(char[] ch, int start, int length,
			boolean isAttVal, Writer out) throws IOException {
		if (isAttVal) {
			for (char c : ch) {
				if (escapeList.length > ((int) c)
						&& escapeList[(int) c] != null) {
					out.write(escapeList[(int) c]);
				} else {
					out.write(c);
				}
			}
		} else {
			if (length >= cdataMiniLength) {
				String s = new String(ch).trim();
				if (s.startsWith("<![CDATA[")
						&& s.endsWith("]]>")) {
					out.write(ch);
					return;
				}
			}
			for (char c : ch) {
				if (c == '"' || c == '\t' || c == '\n') {
					out.write(c);
				} else if (escapeList.length > ((int) c)
						&& escapeList[(int) c] != null) {
					out.write(escapeList[(int) c]);
				} else {
					out.write(c);
				}
			}
		}
	}

}
 

这个类定义了自己的转义方法,并且attribute和非attribute的转义字符数还不一样。另外就是当碰到以  <![CDATA[ 开头和 ]]> 结尾的串时不做转义。

 

最后就是把这个自定义的类应用到Marshaller上去:

		marshaller.setProperty(CharacterEscapeHandler.class.getName(),
				new CustomCharacterEscapeHandler());

最后,再运行输入如下:

<name><![CDATA[Bob]]></name>

对于Unmarshall那端,不需要做什么修改,可以测试一下:

		JAXBContext context = JAXBContext.newInstance(Students.class);
		Unmarshaller unmarshaller = context.createUnmarshaller();
		Students model = (Students) unmarshaller.unmarshal(new File("a.xml"));
		List<StudentType> student = model.getStudent();
		for(StudentType st: student){
			System.out.println(st.getName());
		}

 

分享到:
评论
1 楼 黑暗浪子 2015-06-01  
Caused by: javax.xml.bind.PropertyException: name: com.sun.xml.bind.marshaller.CharacterEscapeHandler
报错

相关推荐

    JAXB工具

    JAXB(Java Architecture for XML Binding)是Java...通过注解,我们可以轻松地控制XML结构,并利用JAXB提供的API进行序列化和反序列化操作。在实际开发中,JAXB常用于Web服务、数据交换以及XML配置文件的处理等场景。

    如何使用JAXB框架定制Web服务行为.doc

    它是一个XML格式的文档,描述了服务提供的操作、输入和输出消息格式,以及它们如何通过网络进行通信。 2. SOAP:作为数据交换的协议,SOAP规定了XML消息的结构,并利用HTTP(或其他协议)进行传输。SOAP消息包含了...

    CamelJAXBExample:骆驼和 JAXB 的简单例子

    5. **Endpoints**: 代表数据输入和输出的地方,例如 HTTP、FTP、MQTT 等。在 "CamelJAXBExample" 中,可能是通过 HTTP 或者文件系统 endpoint 来接收和发送 XML 数据。 6. **Transformers/Processors**: 这些组件...

    cxf-webservice复杂类型

    3. **实现服务接口**:提供服务的实现,处理输入和输出的复杂类型数据。 4. **部署服务**:使用CXF的工具或编程方式部署服务到服务器。 5. **测试与调用**:使用CXF客户端或者SOAP UI等工具进行测试,验证复杂类型...

    SpringMVC关于json、xml自动转换的原理研究.docx

    这里的转换过程涉及到了HttpMessageConverter接口,它是Spring MVC提供的一种标准,用于处理HTTP请求和响应的输入输出数据。 Spring MVC通过`&lt;mvc:annotation-driven/&gt;`标签来启用基于注解的驱动。在解析这个配置时...

    JDK_API_1.6.zip

    JDK 1.6中的Java基础部分主要涉及Java语言的基本语法、数据类型、控制结构、异常处理、多线程和输入输出等核心概念。这些内容构成了Java程序的基础框架,理解和掌握它们是成为一名合格Java程序员的第一步。 1. 类与...

    jackson的6个jar架包

    包括`JsonParser`用于读取JSON输入,以及`JsonGenerator`用于生成JSON输出。它还定义了JSON token流的概念,使得解析和生成过程更高效。 2. **jackson-databind.jar**:这个模块是Jackson的核心功能,实现了将Java...

    jackson相关jar

    核心模块提供了`JsonParser`和`JsonGenerator`两个主要类,分别用于解析JSON输入和生成JSON输出。 3. **Jackson-Core-ASL**: 这个模块可能指的是Jackson 1.x版本的核心库,而现在的最新版本是2.x,其中已经没有了...

    java项目常用jar包

    - Commons IO:包含输入输出流、文件操作、网络I/O等实用工具。 - Commons Collections:扩展Java集合框架,提供更丰富的数据结构和算法。 5. **Log4j**: - 用于日志记录,提供灵活的日志级别控制和多种输出...

    Java2 类库详解

    Java2类库是Java的核心组成部分,为网络、图形用户界面(GUI)、数据库连接、输入输出(I/O)以及安全性等方面提供了强大的支持。 1. **核心类库**:这是Java2类库的基础,包括了Object、String、Arrays等基础类,...

    trang-jar下载

    - 在命令行环境中,使用Java运行JAR文件,指定输入和输出文件,例如: ``` java -jar trang.jar input.xsd output.rnc ``` 这会将`input.xsd`转换为RNC格式的`output.rnc`。 - 可以通过添加额外的参数来控制...

    Java参考文档(可用!)

    Java API涵盖了从基本数据类型、对象创建、输入输出到网络通信、多线程、数据库连接等各个领域的功能。 在Java API文档中,"Java参考文档".JDK_API_1_6_zh_CN.CHM是一个重要的资源,它包含了Java 1.6版本的所有公开...

    Java电影院在线售票系统

    - **控制台交互**:使用`System.out.println()`和`Scanner`类进行控制台输入输出,实现用户与程序的交互。 - **异常处理**:Java的异常处理机制(try-catch-finally)确保程序在遇到错误时能正常运行。 2. **IO流...

    java基础习题经典按例

    Java的IO流系统允许读写文件、网络数据和标准输入输出。学会使用File类、InputStream/OutputStream以及BufferedReader/Writer等,能让你在处理数据时更加得心应手。 6. **线程与并发** Java支持多线程编程,通过...

    使用 Spring-WS 完成的 Web Service (SOAP)

    - **WSDL 配置**:首先,开发者需要定义 WSDL 文件,描述服务的接口、操作、输入输出消息等信息。 - **XSD 定义**:接着,用 XML Schema(XSD)文件定义数据模型,用于验证 SOAP 消息的内容。 - **Java 类映射**:...

    Java学习资料&项目源码&教程,基于java的聊天系统的设计于实现(系统30).zip

    5. **输入输出流**:数据在网络中传输时,需要通过I/O流进行读写。理解InputStream和OutputStream家族,以及如何处理字符编码是必要的。 6. **设计模式**:为了保证代码的可维护性和扩展性,可能会使用到单例模式...

    Java api 中文文档

    3. **IO与NIO**:`java.io`包包含传统输入输出流,如FileInputStream和FileOutputStream,而`java.nio`包则提供了非阻塞I/O,适用于高并发场景。 4. **网络编程**:`java.net`包提供了Socket和ServerSocket,支持...

    JDK_API_1_6.rar_JDK API_JDK_API_1_6

    此外,还有Filter流、缓冲流、对象流等扩展,用于实现更复杂的输入输出操作。 4. **网络编程**:Java的net包提供了处理网络连接的能力,如Socket和ServerSocket类用于TCP/IP通信,URL和URLConnection类用于访问网络...

Global site tag (gtag.js) - Google Analytics