JAXB是数据绑定框架之一,它在SOA的实现(如WebService)中被广泛的应用。这里所说的“绑定”是指XML文档与JAVA对象树之间的关联关系:可以根据JAVA对象树生成XML实例文档,这一过程叫做“编组”;而根据XML实例文档生成JAVA对象树的过程叫做“解组”。与JAXB类似的框架还有XMLBeans和Castor等。
从另一方面来讲,JAXB也是XML解析方式之一。与SAX和DOM等不同的是,在解析的过程中操作的不再是DOM节点,取而代之的是JAVA对象树。
JAXB最核心的接口和类有如下几个:
1)javax.xml.bind.Marshaller:编组器。提供编组、验证、事件处理等操作;
2)javax.xml.bind.Unmarshaller:解组器。提供解组、验证、事件处理等操作;
3)javax.xml.bind.JAXBContext:JAXB客户端的入口。提供了实现 JAXB 所需的解组器、编组器和验证等。
JAXB在核心的基础上提供了验证和事件处理的功能。
验证的目的在于保证编组结果或用于解组的资源数据是符合相关模式(Schema或DTD)规范的。JAXB1.0验证实现采用的是JAXP早期的验证方法,即通过调用Marshaller和Unmarshaller实例对象的setValidating(boolean validating)来控制是否启用验证,不过这种方式在JAXB2.0中已被废弃,取而代之的是采用JAXP的验证分离方式,即通过调用Marshaller和Unmarshaller实例对象的setSchema(Schema schema)方法,设置相关的验证模式即可。(JAXP的验证可参考http://code727.iteye.com/blog/1908320)
默认情况下,JAXB在编组或解组的过程中,如果发现所绑定的数据资源不符合相关的模式约束时,只是简单的抛出MarshalException和UnmarshalException,并会阻止当前编组或解组过程继续往下进行。为了更好的控制验证出现错误时的后续行为以及对错误信息的展示,JAXB提供了一个javax.xml.bind.ValidationEventHandler接口专门来做这些工作,用户需自定义实现此接口,完成后将相关的实例对象赋予Marshaller和Unmarshaller实例对象即可。
从上面UML图中可看出,ValidationEventHandler接口只声明了一个public boolean handleEvent( ValidationEvent event )方法,这个方法不需要在客户端进行显示调用,而是当发生验证错误时,由Marshaller和Unmarshaller实例对象自动回调,此方法返回boolean值,表示是否继续进行后续的处理。
ValidationEventHandler可以处理三种类型的错误,如下:
1)Warning:警告,对应ValidationEvent.WARNING常量标识;
2)Error:一般性错误,对应ValidationEvent.ERROR常量标识;
3)Fatal Error:严重错误,对应ValidationEvent.FATAL_ERROR常量标识。
handleEvent()方法的参数是一个ValidationEvent接口对象,当错误发生时,可通过此对象可获取到错误信息以及错误在文档中的坐标位置(javax.xml.bind.ValidationEventLocator)。
JAXB另外一个核心部分就是模式(Schema或DTD),上述的编组、解组和验证等都是围绕模式来开展的:
1)对于编组来说,模式是根据JAVA对象树生成XML实例文档的依据;
2)对于解组来说,模式是将XML实例数据绑定到JAVA对象树上的依据;
3)更为重要的是,模式是构建(生成)JAVA对象树结构的依据,在解组和编组被执行之前,相关的JAVA对象树结构是必须存在的。
JAXB需要相关的参考实现来支持。例如,SUN的JAVA Web Service开发包(WSDP)就包括了相关的实现,但对于JAXB1.0和2.0来说,各自的实现有着比较显著的区别,如下:
JAXB1.0 | JAXB2.0 | |
定义 |
|
|
主要特征/区别 |
|
|
WSDP可以在https://jaxb.java.net中下载,完成后解压文件,例如:jaxb-ri-2.2.7,在此文件夹里又包含了如下4个主要目录:
1)bin:包含运行模式编译器和生成器的批处理脚本;
2)doc:包含编译器和生成器的说明文档和JAXB API的JAVAdocs;
3)lib:包含JAXB API和参考实现的JAR文件;
4)samples:包含各类JAXB应用程序示例。
如果我们的应用程序是运行在Jdk1.6+版本上的,则无需进行WSDP的安装,其中JAR里的实现已包含在了JRE(rt.jar)中。
在Linux或Window的命令终端中键入如下命令:
xjc -version
如果随后输出xjc的版本信息,则说明JAXB运行环境已准备就绪。
一、JAXB2.0编组实现
利用JAXB进行编组的第一步就是为JAVA对象树模型创建模式(Schema或DTD) 。例如: 一个简易版的工作简历(Resume)模板的模式定义为如下所示:
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.jaxbdemo.daniele.com/resume" xmlns:tns="http://www.jaxbdemo.daniele.com/resume"> <!-- 简历根元素 --> <xsd:element name="resume" type="tns:resume"/> <!-- 简历内容定义 --> <xsd:complexType name="resume"> <xsd:sequence> <xsd:element name="baseInfo" type="tns:baseInfo" /> <xsd:element name="selfAssessment" type="tns:selfAssessment" /> <xsd:element name="careerObjective" type="tns:careerObjective" > <!-- 定义各个location子元素之间的文本值不能重复 --> <xsd:unique name="location" > <xsd:selector xpath=".//location"></xsd:selector> <xsd:field xpath="."></xsd:field> </xsd:unique> </xsd:element> </xsd:sequence> <xsd:attribute name="id" use="optional" /> </xsd:complexType> <!-- "基本信息"部分定义 --> <xsd:complexType name="baseInfo"> <xsd:sequence> <xsd:element name="name" type="xsd:string"/> <xsd:element name="gender" type="tns:gender" /> <xsd:element name="birthDate" type="xsd:date" /> <xsd:element name="residency" type="xsd:string" /> <xsd:element name="experience" type="tns:experience" /> <xsd:element name="email" type="tns:email" /> <xsd:element name="mobilePhone" type="tns:mobilePhone" /> <xsd:element name="height" type="tns:height" minOccurs="0" maxOccurs="1" /> <xsd:element name="maritalStatus" type="tns:maritalStatus" minOccurs="0" maxOccurs="1" /> </xsd:sequence> </xsd:complexType> <!-- "自我评价"部分定义 --> <xsd:complexType name="selfAssessment"> <xsd:sequence> <xsd:element name="description" type="xsd:string"/> </xsd:sequence> </xsd:complexType> <!-- "求职意向"部分定义 --> <xsd:complexType name="careerObjective"> <xsd:sequence> <xsd:element name="jobType" type="tns:jobType" default="Full-time"/> <xsd:element name="location" type="tns:location" minOccurs="1" maxOccurs="5" /> </xsd:sequence> </xsd:complexType> <!-- 性别类型定义 --> <xsd:simpleType name="gender"> <xsd:restriction base="xsd:string"> <xsd:enumeration value="male" /> <xsd:enumeration value="female" /> </xsd:restriction> </xsd:simpleType> <!-- 工作年限定义 --> <xsd:simpleType name="experience"> <xsd:restriction base="xsd:integer"> <xsd:minExclusive value="-1" /> </xsd:restriction> </xsd:simpleType> <!-- 电子邮件定义 --> <xsd:simpleType name="email"> <xsd:restriction base="xsd:string"> <xsd:pattern value="(\w+\.)*\w+@(\w+\.)+[A-Za-z]{2,9}" /> </xsd:restriction> </xsd:simpleType> <!-- 移动电话定义 --> <xsd:simpleType name="mobilePhone"> <xsd:restriction base="xsd:string"> <xsd:pattern value="\d{11}" /> </xsd:restriction> </xsd:simpleType> <!-- 身高定义 --> <xsd:simpleType name="height"> <xsd:restriction base="xsd:int"> <xsd:minExclusive value="0" /> <xsd:maxExclusive value="300" /> </xsd:restriction> </xsd:simpleType> <!-- 婚姻状况定义 --> <xsd:simpleType name="maritalStatus"> <xsd:restriction base="xsd:string"> <xsd:enumeration value="married" /> <xsd:enumeration value="unmarried" /> </xsd:restriction> </xsd:simpleType> <!-- 工作性质定义 --> <xsd:simpleType name="jobType"> <xsd:restriction base="xsd:string"> <xsd:enumeration value="Full-time" /> <xsd:enumeration value="Part-time" /> <xsd:enumeration value="Intern/Trainee" /> <xsd:enumeration value="Both Full-time And Part-time" /> </xsd:restriction> </xsd:simpleType> <!-- 工作地点定义 --> <xsd:simpleType name="location"> <xsd:restriction base="xsd:string"> <xsd:minLength value="1" /> <!-- 移除开头和结尾部分的空格,而中间多个连续的空格会被缩减为一个的空格 --> <xsd:whiteSpace value="collapse" /> </xsd:restriction> </xsd:simpleType> </xsd:schema>根据JAXB2.0的规范,第二步则是编译上述的Schema并生成对应的JAVA对象树模式,即相关的POJO类。这里需要使用xjc来编译,例如:
xjc -p com.daniele.jaxbdemo.resume.domain -d src resource/resume.xsd
表示将resource/resume.xsd生成的基础类放到src输出目录下的com.daniele.jaxbdemo.resume.domain包中。其中-p和-d是命令行参数,更多的参数可参考WSDP的文档。
图2 xjc编译生成的类
从图2中可看出还生成了一个ObjectFacotory,它是一个专门创建complexType元素的工厂类。上述两步确认无误后就可以进行相关的编组实现了。
编组实现的基础步骤如下:
1)按照Schema规则设置JAVA对象树各节点值;
2)创建JAXBContext实例;
3)根据Context实例创建Marshaller实例;
4)创建用于输出目的的JAXBElement实例;
5)调用Marshaller实例的marshal()方法,并将上述的JAXBElement实例传递给此方法。
package com.daniele.jaxbdemo.resume.test; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import org.xml.sax.SAXException; import com.daniele.jaxbdemo.resume.domain.BaseInfo; import com.daniele.jaxbdemo.resume.domain.CareerObjective; import com.daniele.jaxbdemo.resume.domain.Gender; import com.daniele.jaxbdemo.resume.domain.JobType; import com.daniele.jaxbdemo.resume.domain.MaritalStatus; import com.daniele.jaxbdemo.resume.domain.ObjectFactory; import com.daniele.jaxbdemo.resume.domain.Resume; import com.daniele.jaxbdemo.resume.domain.SelfAssessment; import com.daniele.jaxbdemo.validation.JAXBValidationEventHandler; import com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpl; /** * <p>简历模板编组测试类</p> * @author <a href="mailto:[email protected]">Daniele</a> * @version 1.0.0, 2013-7-29 * @see * @since JAXBDemo1.0.0 */ public class ResumeMarshallerTest { public static void main(String[] args) throws JAXBException, IOException, SAXException { ObjectFactory factory = new ObjectFactory(); /* 设置"基础信息"部分的数据 */ BaseInfo baseInfo = factory.createBaseInfo(); baseInfo.setName("daniele"); baseInfo.setGender(Gender.MALE); baseInfo.setBirthDate(XMLGregorianCalendarImpl.createDate(1983, 7, 27, 0)); baseInfo.setExperience(new BigInteger("7")); baseInfo.setEmail("[email protected]"); baseInfo.setMobilePhone("13888888888"); baseInfo.setResidency("ChengDu Hi Tech Zone"); baseInfo.setHeight(171); baseInfo.setMaritalStatus(MaritalStatus.MARRIED); /* 设置"自我描述"部分的数据 */ SelfAssessment selfAssessment = factory.createSelfAssessment(); selfAssessment.setDescription("5 years java development and 2 years test management."); /* 设置"求职意向"部分的数据 */ CareerObjective careerObjective = factory.createCareerObjective(); careerObjective.setJobType(JobType.FULL_TIME); List<String> location = new ArrayList<String>(); location.add("ChengDu"); location.add("DuJiangYan"); careerObjective.setLocation(location); /* 设置根元素<Resume>下的各个子元素 */ Resume resume = factory.createResume(); resume.setBaseInfo(baseInfo); resume.setSelfAssessment(selfAssessment); resume.setCareerObjective(careerObjective); // 根据上下文路径(用于绑定目的的POJO类所在的包路径)创建JAXB上下文 JAXBContext context = JAXBContext.newInstance("com.daniele.jaxbdemo.resume.domain"); Marshaller marshaller = context.createMarshaller(); File schemaSource = new File("resource/resume.xsd"); Schema schema = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(schemaSource); // 设置编组过程中所遵循的验证规则。如果不设置,则不能保证最终的编组结果是合法的。 marshaller.setSchema(schema); JAXBValidationEventHandler handler = new JAXBValidationEventHandler(); handler.setContinueWhenWarning(true); handler.setContinueWhenError(false); handler.setContinueWhenFatalError(false); /* * 设置在编组过程中所使用的验证事件处理器。 * 当Marshaller实例对象中设置了schema对象,并且在编组过程中出现验证警告或错误时, * 则会自动调用处理器的handleEvent()方法进行处理, */ marshaller.setEventHandler(handler); // 创建可用于编组输出的根元素 JAXBElement<Resume> root = factory.createResume(resume); // 在最终的编组输出中增加xsi:schemaLocation属性,并设置值为Schema文件的URI marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, root.getName() .getNamespaceURI() + " " + schemaSource.getName()); // marshaller.setProperty(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION, schemaSource.getName()); // 设置编组后进行格式化输出 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); // 编组输出方式一:根据JAXBElement类型的根元素来编组 // marshaller.marshal(root, new FileOutputStream(new File( // "resource/resume_daniele.xml"))); /* * 编组输出方式二:根据某个POJO类实例所表示的根元素来编组 * 注意:利用xjc命令在生成POJO类时,没有使用@XmlRootElement注解来将某一个类标注为XML根元素, * 因此这里需要在Resume类上人为的加入此注解。 */ marshaller.marshal(resume, new FileOutputStream( new File("resource/resume_daniele.xml"))); } }
package com.daniele.jaxbdemo.validation; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import javax.xml.bind.ValidationEvent; import javax.xml.bind.ValidationEventHandler; import javax.xml.bind.ValidationEventLocator; /** * <p>JAXB编组或解组验证事件处理器</p> * @author <a href="mailto:[email protected]">Daniele</a> * @version 1.0.0, 2013-7-29 * @see * @since JAXBDemo1.0.0 */ public class JAXBValidationEventHandler implements ValidationEventHandler { private static final String WARNING_EVENT = "Warning"; private static final String ERROR_EVENT = "Error"; private static final String FATAL_ERROR_EVENT = "FatalError"; private Writer writer; private StringBuffer message; /** 当出现警告级别的验证问题时是否还继续后续处理 */ private boolean continueWhenWarning = true; /** 当出现一般性错误级别的验证问题时是否还继续后续处理 */ private boolean continueWhenError = false; /** 当出现致命性错误级别的验证问题时是否还继续后续处理 */ private boolean continueWhenFatalError = false; public JAXBValidationEventHandler() { this(System.out); } public JAXBValidationEventHandler(OutputStream out) { writer = new BufferedWriter(new OutputStreamWriter(out)); message = new StringBuffer(); } /** * <p>处理编组或解组过程中出现的验证警告或错误的通知。</p> * @author <a href="mailto:[email protected]">Daniele</a> * @param event:事件对象 * @return * @since JAXBDemo1.0.0 */ @Override public boolean handleEvent(ValidationEvent event) { int severity = event.getSeverity(); try { if (severity == ValidationEvent.WARNING) return handleWarning(event); else if (severity == ValidationEvent.ERROR) return handleError(event); else return handleFatalError(event); } catch (Exception e) { e.printStackTrace(); } return false; } /** * <p>处理警告级别的验证事件</p> * @author <a href="mailto:[email protected]">Daniele</a> * @param event:事件对象 * @return * @throws IOException * @since JAXBDemo1.0.0 */ protected boolean handleWarning(ValidationEvent event) throws IOException { handleEventMessage(event, WARNING_EVENT); return continueWhenWarning; } /** * <p>处理一般性错误级别的验证事件</p> * @author <a href="mailto:[email protected]">Daniele</a> * @param event:事件对象 * @return * @throws IOException * @since JAXBDemo1.0.0 */ protected boolean handleError(ValidationEvent event) throws IOException { handleEventMessage(event, ERROR_EVENT); return continueWhenError; } /** * <p>处理严重性错误级别的验证事件</p> * @author <a href="mailto:[email protected]">Daniele</a> * @param event:事件对象 * @return * @throws IOException * @since JAXBDemo1.0.0 */ protected boolean handleFatalError(ValidationEvent event) throws IOException { handleEventMessage(event, FATAL_ERROR_EVENT); return continueWhenFatalError; } /** * <p>根据事件级别处理提示信息</p> * @author <a href="mailto:[email protected]">Daniele</a> * @param event:事件对象 * @param eventLevel:事件级别名称 * @throws IOException * @since JAXBDemo1.0.0 */ protected void handleEventMessage(ValidationEvent event, String eventLevel) throws IOException { String eventMessage = event.getMessage(); ValidationEventLocator locator = event.getLocator(); message.setLength(0); message.append(eventLevel).append("[").append(locator.getLineNumber()) .append(",").append(locator.getColumnNumber()).append("]"); int index = eventMessage.indexOf(":"); if (index != -1) message.append(eventMessage.substring(index)); else message.append(":").append(eventMessage); message.append("\n"); writer.write(message.toString()); writer.flush(); } public boolean isContinueWhenWarning() { return continueWhenWarning; } public void setContinueWhenWarning(boolean continueWhenWarning) { this.continueWhenWarning = continueWhenWarning; } public boolean isContinueWhenError() { return continueWhenError; } public void setContinueWhenError(boolean continueWhenError) { this.continueWhenError = continueWhenError; } public boolean isContinueWhenFatalError() { return continueWhenFatalError; } public void setContinueWhenFatalError(boolean continueWhenFatalError) { this.continueWhenFatalError = continueWhenFatalError; } }上述ResumeMarshallerTest运行后将会在resource目录中自动生成resume_daniele.xml实例文档:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ns2:resume xmlns:ns2="http://www.jaxbdemo.daniele.com/resume" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.jaxbdemo.daniele.com/resume resume.xsd"> <baseInfo> <name>daniele</name> <gender>male</gender> <birthDate>1983-07-27Z</birthDate> <residency>ChengDu Hi Tech Zone</residency> <experience>7</experience> <email>[email protected]</email> <mobilePhone>13888888888</mobilePhone> <height>171</height> <maritalStatus>married</maritalStatus> </baseInfo> <selfAssessment> <description>5 years java development and 2 years test management.</description> </selfAssessment> <careerObjective> <jobType>Full-time</jobType> <location>ChengDu</location> <location>DuJiangYan</location> </careerObjective> </ns2:resume>
二、JAXB2.0解组实现
与编组实现类似,解组实现的基础步骤如下:
1)创建JAXBContext实例;
2)根据Context实例创建Unmarshaller实例;
3)调用Unmarshaller实例的unmarshal()方法,将解组的XML实例文档资源传递给此方法,并返回绑定了实例数据的JAXBElement实例对象;
4)从JAXBElement实例对象中获取数据。
package com.daniele.jaxbdemo.resume.test; import java.io.File; import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import org.xml.sax.SAXException; import com.daniele.jaxbdemo.resume.domain.BaseInfo; import com.daniele.jaxbdemo.resume.domain.CareerObjective; import com.daniele.jaxbdemo.resume.domain.MaritalStatus; import com.daniele.jaxbdemo.resume.domain.Resume; import com.daniele.jaxbdemo.validation.JAXBValidationEventHandler; /** * <p>简历模板解组测试类</p> * @author <a href="mailto:[email protected]">Daniele</a> * @version 1.0.0, 2013-7-29 * @see * @since JAXBDemo1.0.0 */ public class ResumeUnmarshallerTest { public static void main(String[] args) throws JAXBException, SAXException { JAXBContext context = JAXBContext.newInstance("com.daniele.jaxbdemo.resume.domain"); Unmarshaller unmarshaller = context.createUnmarshaller(); File schemaSource = new File("resource/resume.xsd"); Schema schema = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(schemaSource); // 设置解组过程中所遵循的验证规则。如果不设置,则不能保证最终的解组结果是合法的。 unmarshaller.setSchema(schema); JAXBValidationEventHandler handler = new JAXBValidationEventHandler(); handler.setContinueWhenWarning(true); handler.setContinueWhenError(false); handler.setContinueWhenFatalError(false); /* * 设置在解组过程中所使用的验证事件处理器。 * 当Unmarshaller实例对象中设置了schema对象,并且在解组过程中出现验证警告或错误时, * 则会自动调用处理器的handleEvent()方法进行处理, */ unmarshaller.setEventHandler(handler); JAXBElement<Resume> resumeElement = unmarshaller.unmarshal( new StreamSource(new File("resource/resume_daniele.xml")), Resume.class); Resume resume = resumeElement.getValue(); System.out.println("简历详情如下:"); BaseInfo info = resume.getBaseInfo(); System.out.println("------ 【基本信息】 ------"); System.out.println("name :" + info.getName()); System.out.println("gender :" + info.getGender()); System.out.println("birthDate :" + info.getBirthDate()); System.out.println("residency :" + info.getResidency()); System.out.println("email :" + info.getEmail()); System.out.println("mobilePhone:" + info.getMobilePhone()); int height = info.getHeight(); if (height > 0) System.out.println("height :" + height); MaritalStatus maritalStatus = info.getMaritalStatus(); if (maritalStatus != null) System.out.println("maritalStatus:" + maritalStatus.value()); System.out.println("------ 【个人评价】 ------"); System.out.println(resume.getSelfAssessment().getDescription()); CareerObjective careerObjective = resume.getCareerObjective(); System.out.println("------ 【求职意向】 ------"); System.out.println("jobType :" + careerObjective.getJobType().value()); System.out.println("location:" + careerObjective.getLocation()); } }考虑如下一个场景:实际的解组应用通常都发生客户端,我们往往只会接收到服务端发送来的一个XML实例文档,而不知道JAVA对象树模型究竟是怎样的,因此就不能像上述ResumeUnmarshallerTest类里所展示的那样直接从对象树中获取数据(因为在客户端需要一个能接收当前XML实例数据的对象树后,才能获取到被封装的数据),为了解决这个问题,我们需要根据XML实例文档来生成Shcema,然后再从Shcema中生成对象树模型。
因此如何根据XML实例文档来生成Shcema就成为了关键,可以借助第三方的工具来完成,如http://www.xmlforasp.net/CodeBank/System_Xml_Schema/BuildSchema/BuildXMLSchema.aspx,
如上图,可以在第一个文本框中输入完整的XML实例文档内容后点击“Generate Schema”按钮,等待一段时间,页面会自动生成下面第二个文本框以及Shema结构定义的内容。
除此之外,在实际的应用中可能还需要根据带了JAXB注解的对象树来生成XML Schema,为此参考实现提供了shcemagen命令来完成这一工作。