从XML到Java代码的数据绑定之二 从XML数据建设类
副标题#e#
数据绑定系列的第二篇是如何从 XML 数据限制中生成一个 Java 语言。 本文通过完整的代码揭示了如何生成类和代码,并提供了如何定制您本身版本的发起。 还没有看过第一篇吗?第一篇, "工具,无处不在的工具", 表明白数据绑定是如何将 XML 和 Java 语言工具互为转换。它较量了数据绑定和其它在 Java 措施中处理惩罚 XML 的要领, 并先容了一个 XML 设置文档示例。第一部门也先容了利用 XML Schema 来约束数据。
在深入 Java 措施和 XML 代码之前,先快速回首一下本系列第一部门所打下的基本。
在第一部门中,我们知道只要可以标识文档的一组约束,就可以将文档转换成 Java 工具。那些约束为数据提供了接口。如 Web 处事设置文档示例中所示,XML 文档该当成为现有 Java 类的一个实例,而且从数据约束生成谁人类。最后,会看到暗示样本 XML 文档约束的 XML schema。
假如对细节尚有疑问,请回首 第一篇文章.
打造基本
此刻,可以着手从 XML schema 建设 Java 类。该类必需精确暗示数据约束,并提供 Java 应用措施将利用的简朴读要领和写要领。开始之前,让我们先回首清单 1,查察为 WebServiceConfiguration 文档界说的 XML schema。
清单 1. 暗示 Web 容器设置文档数据接口的 XML schema
<?xml version="1.0"?>
<schema targetNamespace="http://www.enhydra.org"
xmlns="http://www.w3.org/1999/xmlSchema"
xmlns:enhydra="http://www.enhydra.org"
>
<complexType name="ServiceConfiguration">
<attribute name="name" type="string" />
<attribute name="version" type="float" />
</complexType>
<element name="serviceConfiguration" type="ServiceConfiguration" />
<complexType name="WebServiceConfiguration"
baseType="ServiceConfiguration"
derivedBy="extension">
<element name="port">
<complexType>
<attribute name="protocol" type="string" />
<attribute name="number" type="integer" />
<attribute name="protected" type="string" />
</complexType>
</element>
<element name="document">
<complexType>
<attribute name="root" type="string" />
<attribute name="index" type="string" />
<attribute name="error" type="string" />
</complexType>
</element>
</complexType>
<element name="webServiceConfiguration" type="WebServiceConfiguration" />
</schema>
生成代码
开始生成 Java 代码之前,首先必需确定焦点类的名称。将利用 org.enhydra.xml.binding 包中的 SchemaMapper,它是 Enhydra 应用处事器实用措施类荟萃的一部门。还可以将任何须须的支持类放到这个包中。
除了类名称以外,还必需确定用来读取和建设 XML 的 Java API。如上一篇文章中所接头过的,三种主要选择是 SAX、DOM 和 JDOM。由于 SAX 仅仅合用于读取 XML 文档,因此它不适合建设 XML。由于在打包阶段中要将 Java 工具转换为 XML 暗示,因此在此阶段中需要建设 XML。这就将选择的范畴缩小到 DOM 和 JDOM。在这两种选择都可用的环境下,本例中我选择利用 JDOM API,仅为了显示其成果性(并不只仅因为我是它的合著者之一!)。
最后,必需指出如何将 XML schema 提供应 SchemaMapper 类。凡是,可以假设类的生成是脱机完成的(通过静态 main 要领)。仅通过使 main 要领挪用非静态要领,还可以从运行时情况中利用类。做了这些抉择后,就可以开始勾画类的框架了。
#p#副标题#e#
组装 SchemaMapper 类框架
要做的第一件事就是为要生成的代码配置一些根基存储器。必需可以或许从每个执行映射的 XML schema 生成多个接口和实现。Java HashMap 正好满意要求。键是接口或实现名称以及映射表中的值,该值是将要输出到新 Java 措施文件的实际代码。还需要存储每对接口/实现的属性(属性是在这两种类之间共享的)。这里,我再次利用 HashMap。个中,键是接口名称。可是,由于每个接口大概有多个属性,因此该值是另一个具有属性及其范例的 HashMap。最后,必需存储 XML schema 的名称空间,因为 JDOM 将利用这个名称空间来会见 XML schema 中的布局。所有这些详细信息都足以劈头勾画出新类的框架,新类在清单 2 中。
还请留意在清单 2 中已添加了两个需要利用的根基要领:个中一个要领需要利用 XML schema 的 URL 来执行生成(答允它在网络可会见 schema 以及当地 schema 下运行),另一个要领将类输出到指定的目次中。最后,简朴的 main 要领将 XML schema 看作一个变量,然后执行生成。
清单 2. SchemaMapper 类的框架
#p#分页标题#e#
package org.enhydra.xml.binding;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.List;
// JDOM classes used for document representation
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.NoSuchAttributeException;
import org.jdom.NoSuchChildException;
import org.jdom.input.SAXBuilder;
/**
<p>
<code>SchemaMapper</code> handles generation of Java interfaces and classes
from an XML schema, essentially allowing data contracts to be set up
for the binding of XML instance documents to Java objects.
</p>
@author Brett McLaughlin
/
public class SchemaMapper {
/** Storage for code for interfaces */
private Map interfaces;
/** Storage for code for implementations */
private Map implementations;
/** Properties that accessor/mutators should be created for */
protected Map properties;
/** XML Schema Namespace */
private Namespace schemaNamespace;
/** XML Schema Namespace URI */
private static final String SCHEMA_NAMESPACE_URI =
"http://www.w3.org/1999/xmlSchema";
/**
* <p>
*Allocate storage and set up defaults.
* </p>
*/
public SchemaMapper() {
interfaces = new HashMap();
implementations = new HashMap();
properties = new HashMap();
schemaNamespace = Namespace.getNamespace(SCHEMA_NAMESPACE_URI);
}
/**
* <p>
*This is the "entry point" for generation of Java classes from an XML
*Schema. It allows a schema to be supplied, via <code>URL</code>,
*and that schema is used for input to generation.
* </p>
*
* @param schemaURL <code>URL</code> at which XML Schema is located.
* @throws <code>IOException</code> - when problems in generation occur.
*/
public void generateClasses(URL schemaURL) throws IOException {
// Perform generation
}
/**
* <p>
*This will write out the generated classes to the supplied stream.
* </p>
*
* @param directory <code>File</code> to write to (should be a directory).
* @throws <code>IOException</code> - when output errors occur.
*/
public void writeClasses(File dir) throws IOException {
// Perform output to files
}
/**
* <p>
*This provides a static entry point for class generation from
*XML Schemas.
* </p>
*
* @param args <code>String[]</code> list of files to parse.
*/
public static void main(String[] args) {
SchemaMapper mapper = new SchemaMapper();
try {
for (int i=0; i<args.length; i++) {
File file = new File(args[i]);
mapper.generateClasses(file.toURL());
mapper.writeClasses(new File("."));
}
} catch (FileNotFoundException e) {
System.out.println("Could not locate XML Schema: ");
e.printStackTrace();
} catch (IOException e) {
System.out.println("Java class generation failed: ");
e.printStackTrace();
}
}
}
In 清单 2中,可以看到对付每个作为自变量通报的 XML schema,main 要领都挪用生成进程。首先,要了解生成类。将文件名转换为 URL,并通报到 generateClasses(URL schemaURL) 。然后,通过 writeClasses(File dir) 要领将类写到当前目次中(转换成 Java File: new File("."))。
任何其它 Java 类都可以在运行时举办沟通的挪用,并生成类。譬喻,一个定制类装入器也许能发明需要打包,确定仍要生成的接口和实现,并利用 SchemaMapper 类来执行该任务。所有这一切都在运行时完成。因为 generateClasses() 要领需要一个 URL,所以在网络上利用这个类很是简朴。譬喻,可以利用它来请求从 HTTP 上果真可用的 XML schema 生成类。
由于对如何利用类做了只管少的假设,因此它是一个普通类;措施可以同时在当地和长途利用它。而且这个类可以看成一组 Java 语言和 XML 实用措施类的一部门,而不是必需以某种非凡形式利用的专用类。这种可重用性原则对 XML 出格要害,因为在差异系统长举办网络会见和通信是 XML 的根基前提。
生成类
构建好类的框架后,就可以添加类的主体了。
#p#分页标题#e#
我已经提到过生成进程具有递归性质。请记着这一点,需要填充 generateClasses() 要领才气开始。可以利用 JDOM 读取 XML schema,然后从 schema 中抽取每个 complexType 元素。对付这些元素中的每一个,如清单 3 所示,递归历程从 handleComplexType() 挪用处开始(今后将进一步接头)。
清单 3. The generateClasses() 要领
public void generateClasses(URL schemaURL) throws IOException {
/**
* Create builder to generate JDOM representation of XML Schema,
* without validation and using Apache Xerces.
*/
SAXBuilder builder = new SAXBuilder();
try {
Document schemaDoc = builder.build(schemaURL);
// Handle complex types
List complexTypes = schemaDoc.getRootElement()
.getChildren("complexType",
schemaNamespace);
for (Iterator i = complexTypes.iterator(); i.hasNext(); ) {
// Iterate and handle
Element complexType = (Element)i.next();
handleComplexType(complexType);
}
} catch (JDOMException e) {
throw new IOException(e.getMessage());
}
}
为轻便起见,将强调一些重点,而不是具体叙述将 schema 转换为 Java 类的整个进程。可以 联机查察完整的 SchemaMapper 类,可能可以 下载它。
生成器必需确定在 XML schema 中找到的每个 complexType 元素是 显式的(具有“范例”属性),照旧 隐式的(没有“范例”属性)。假如范例是显式的,则范例将成为接口名称,而且首字母大写。假如范例是隐式的,那么将按照特性名称结构接口名称。清单 4 中显示了处理惩罚这个逻辑的代码段。(如要相识更大都据绑定的界说,请参阅侧栏, 术语表明。)
清单 4. 确定接口名称
// Determine if this is an explict or implicit type
String type = null;
// Handle extension, if needed
String baseType = null;
try {
// Assume that we are dealing with an explicit type
type = complexType.getAttribute("name")
.getValue();
} catch (NoSuchAttributeException e) {
/*
* It is safe with an implicit type to assume that the parent
* is of type "element", has no "type" attribute, and that we
* can derive the type as the value of the element's "name"
* attribute with the word "Type" appended to it.
*/
try {
type = new StringBuffer()
.append(BindingUtils.initialCaps(
complexType.getParent()
.getAttribute("name")
.getValue()))
.append("Type")
.toString();
} catch (NoSuchAttributeException nsae) {
// Shouldn't happen in schema-valid documents
throw new IOException("All elements must at have a name.");
}
}
因此,按照代码中的定名约定, 具有ServiceConfiguration 范例的元素将生成名为 ServiceConfiguration 的 Java 接口。名为 port 但 没有显式范例的元素将生成叫做 PortType 的 Java 接口。它回收元素名称 ( port ),将首字母转成大写 ( Port ),再加上单词 Type ,就获得了 PortType 。
同样,所有实现类都利用接口名称,然后添加缩写 Impl 。所以,最终实现类是 ServiceConfigurationImpl 和 PortTypeImpl 。
利用这些定名约定,您可以很容易地确定将数据约束映射到 Java 接口会获得哪些 Java 类。假如配置了应用措施在运行时装入类,那么类装入器或其它实用措施可以迅速确定是否已装入了所需的类。类装入器或实用措施只要从 XML schema 中找出生成的类名称,然后实验装入它们就可以了。定名逻辑是事先确定的,因此查抄起来很是利便。
一旦确定了名称,就可以生成接口和实现类的框架(请参阅清单 5)。
清单 5. 生成代码
StringBuffer interfaceCode = new StringBuffer();
StringBuffer implementationCode = new StringBuffer();
/*
* Start writing out the interface and implementation class
* definitions.
*/
interfaceCode.append("public interface ")
.append(interfaceName);
// Add in extension if appropriate
if (baseType != null) {
interfaceCode.append(" extends ")
.append(baseType);
}
interfaceCode.append(" {\n");
implementationCode.append("public class ")
.append(implementationName);
// Add in extension if appropriate
if (baseType != null) {
implementationCode.append(" extends ")
.append(baseType)
.append("Impl");
}
implementationCode.append(" implements ")
.append(interfaceName)
.append(" {\n");
// Add in properties and methods
// Close up interface and implementation classes
interfaceCode.append("}");
implementationCode.append("}");
#p#分页标题#e#
实际上,生成属性和要领是相当简朴的。将接口和相应实现的名称添加到类的存储器中,然后是右花括号,它们的浸染是竣事类。像这样成对生成类,而不是单独生成类,将使同时在接口和实现反应出该进程变得简朴。查抄源代码(请参阅 参考资料),就可以获得足够的表明。
清单 5中的粗体注释暗示源列表中的多行代码。在这里精简代码是为了保持简捷。对付正在建设的 XML schema 的每个特性(由 schema attribute 暗示),城市将读要领和写要领添加到接口和实现(实现尚有执行要领逻辑的代码)。同时,将为实现类的代码添加变量。
最终功效就是本系列第一部门中生成的类。可以在这里查察它们,可能与本文中的其余代码一起下载(请参阅 参考资料):
ServiceConfiguration.java
ServiceConfigurationImpl.java
PortType.java
PortTypeImpl.java
DocumentType.java
DocumentTypeImpl.java
WebServiceConfiguration.java
WebServiceConfigurationImpl.java
有两个帮助措施类也将参加类生成:
BindingUtils ,将首字母酿成大写。固然,可以将这个要领添加到生成器类,但我规划今后在打包息争包类时再利用该要领,所以我将它归到这个帮助措施类中。可以 联机查察 BindingUtils ,可能可以 下载它。
DataMapping , SchemaMapper 类用来转换数据范例。可以 联机查察源码可能 下载源码。
完成包
如很多其它开放源码软件,在这里显示的数据绑定包是一项正在举办中的事情。固然它已经初具局限,但仍有很大空间可用于添加更多成果和做改造。因此,以这段代码为基本,可以有很多方法应用措施中加以衍生。
可以从头利用该样本代码,以将 XML schema 的数据约束转换为范例安详的 Java 接口和实现。譬喻,迄今为止,示例代码还没有处理惩罚 XML schema 中大概指定的范畴。而对付很多 XML 开拓人员,那些数据范畴才是利用 schema 的真正原因。然后,请思量清单 6 中 Web 处事的扩充 XML schema。
清单 6. 带扩充约束的 Web 处事设置
<?xml version="1.0"?>
<schema targetNamespace="http://www.enhydra.org"
xmlns="http://www.w3.org/1999/xmlSchema"
xmlns:enhydra="http://www.enhydra.org"
>
<complexType name="ServiceConfiguration">
<attribute name="name" type="string" />
<attribute name="version" type="float" />
</complexType>
<element name="serviceConfiguration" type="ServiceConfiguration" />
<complexType name="WebServiceConfiguration"
baseType="ServiceConfiguration"
derivedBy="extension">
<element name="port">
<complexType>
<attribute name="protocol" type="string" />
<attribute name="number">
<simpleType base="integer">
<minExclusive value="0" />
<maxInclusive value="32767" />
</simpleType>
</attribute>
<attribute name="protected" type="string" />
</complexType>
</element>
<element name="document">
<complexType>
<attribute name="root" type="string" />
<attribute name="index" type="string" />
<attribute name="error" type="string" />
</complexType>
</element>
</complexType>
<element name="webServiceConfiguration" type="WebServiceConfiguration" />
</schema>
清单 6说明白number属性的范例, 而且在用赤色强调的几行中指定了值的正当范畴(1 到 32,767)。当前版本的 SchemaMapper 将忽略这些附加声明。从 schema 建设 Java 接口和实现类时,没有须要处理惩罚 XML schema 中的 minXXX 和 maxXXX 要害字,但它们可以增加相当多的附加验证。
请查察清单 7 中的代码示例,这些代码是可在实现类中生成的代码,以确保只有 schema 指定范畴中的值可以作为变量。
清单 7. 带范畴查抄的生成代码
#p#分页标题#e#
public class PortTypeImpl implements PortType {
private String protocol;
private int number;
private String protected;
public void setNumber(int number) {
if ((number > 0) && (number <= 32767)) {
this.number = number;
} else {
throw IllegalArgumentException("Argument must be greater than 0
and less than or equal to 32767");
}
}
public int getNumber() {
return number;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getProtocol() {
return protocol;
}
public void setProtected(String protected) {
this.protected = protected;
}
public String getProtected() {
return protected;
}
}
假如对类提供了犯科值,那么清单 7 中的生成代码块将抛出一个运行时异常,这样既确保了范例安详性又确保了范畴安详性。
可以很利便地将雷同于清单 6 和清单 7 中的加强部门添加到我提供的根基代码中,因为本文中的所有代码完全都是开放源码。您也许还想插手 Enhydra 体系布局事情组邮件发送清单,在该清单中维护和接头了该代码的将来版本和修订本。可以从 Enhydra Web 站点上插手该清单,列在本文的 参考资料中。
总结
今朝为止,应该已经相识什么是数据绑定。已知道利用数据绑定的原因,出格是设置信息。已经把握如何建设 XML schema 和设置 Web 容器处事的 XML 实例文档,并且我们已经具体接头了 org.enhydra.xml.binding.SchemaMapper 类。利用这个类,您可以建设 Java 接口和(该接口的)实现,它将打点从 XML 文档建设的 Java 实例。还知道如何将约束从 XML schema 映射到 Java。
此刻,已经可以进入下一部门。在下一部门中,将开始把 XML 文档实际转换为 Java 工具的进程,个中 Java 工具是生成类的实例。下一篇文章将说明如何完成这个进程,及其逆向进程,以及 org.enhydra.xml.binding.Unmarshaller 和 org.enhydra.xml.binding.Marshaller 类。这两个类将磁盘上文本的 XML 名目数据移到内存中的 Java 暗示,然后再移返来。
但愿您能喜欢 XML schema 生成类,下次再见!
以上所有源码均附在文档开始处的源代码下载链接中。
本文配套源码