• 沒有找到結果。

Generating a Schema from Java

在文檔中 Java SOA Cookbook (頁 101-106)

JAXB Annotations

2.12 Generating a Schema from Java

Problem

You have a Java class and you want to generate an XML schema that matches it.

Solution

Run the command-line tool schemagen, which comes with Java SE 6. Alternatively, generate a schema at runtime with SchemaOutputResolver.

XML schemas can be generated from Java classes. You can use the annotations in the javax.xml.bind.annotations package to give the marshaler hints during the generation process. The schemagen utility comes with the reference implementation of JAXB, and so you can also find it among the Java SE 6 tools. Typing schemagen at the command line should give you usage information.

XMLAccessorType.

XMLElement.

Using schemagen

In its simplest form, you can use schemagen by passing the name of the class you want to create a schema for. To invoke schemagen on Windows, you can use the batch file, and on Linux use the shell script. Here is the quickest example:

> schemagen Product.java

You may run into problems using schemagen on Windows XP Service Pack 2 with JDK 1.6.0. This was filed as bug 6510966. Thanks to Brian Lee for discovering this.

This will first compile the specified Java class, and then write out a new XSD file called schema1.xsd in the current directory by default. To override the directory in which schemagen places its output, use the -d option. Finally, the Product class does not have any external dependencies, but if it did, you would need to specify those with the -cp option. Example 2-16 is what your Java source file looks like.

Example 2-16. Plain Product.java class with no annotations package com.soacookbook.ch02.schemagen;

import java.util.Date;

public class Product {

private static final long serialVersionUID = 12345L;

private String name;

private Date mfrDate;

public Product() { }

public Date getMfrDate() { return mfrDate;

}

public void setMfrDate(Date mfrDate) { this.mfrDate = mfrDate;

}

public String getName() { return name;

}

public void setName(String name) { this.name = name;

} }

Note that schemagen will ignore private fields in classes that do not also specify getter and setter methods for them. The output after invoking schemagen looks like that shown in Example 2-17.

Example 2-17. Schema generated by schemagen for Product.java

<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:complexType name="product">

<xs:sequence>

<xs:element name="mfrDate" type="xs:dateTime" minOccurs="0"/>

<xs:element name="name" type="xs:string" minOccurs="0"/>

</xs:sequence>

</xs:complexType>

</xs:schema>

There are a few interesting things to discuss here. First, a single complexType, not an element, was created for your class. Also, there is no target namespace for the schema.

Next, the serialVersionUID was ignored, which is intuitive given the fact that it is pri-vate, but also given that static fields are not serialized as part of regular Java serialization.

So even if the field had not been private, its static status is enough to keep it from being generated into the schema.

The standard schema type xs:dateTime was used to represent the java.util.Date ob-ject, and schemagen kept the fields in the same order.

You can decorate your Java class with standard JAXB annotations in order to rearrange your results a little. Here are the changes you’ll make:

• Specify a different name for the generated file

• Specify a different name for the class

• Specify a namespace

• Make a global root element

• Keep your static field and maintain its value

Example 2-18 is the same file, but now with JAXB annotations to meet your new goals.

Example 2-18. Product Java class annotated for schema generation package com.soacookbook.ch02.schemagen;

import java.util.Date;

import javax.xml.bind.annotation.XmlElement;

import javax.xml.bind.annotation.XmlRootElement;

import javax.xml.bind.annotation.XmlType;

@XmlRootElement(namespace="com.soacookbook.ch02.schemagen", name="Product2")

@XmlType(namespace="com.soacookbook.ch02.schemagen") public class ProductAnnotated {

private static final long serialVersionUID = 12345L;

@XmlElement(defaultValue="1.0") static String VERSION = "1.0";

private String name;

private Date mfrDate;

public ProductAnnotated() { } //... getters and setters omitted }

The output of running schemagen this time is a single file shown in Example 2-19.

Example 2-19. Schema file generated using JAXB annotations

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<xs:schema version="1.0"

targetNamespace="com.soacookbook.ch02.schemagen"

xmlns:tns="com.soacookbook.ch02.schemagen"

xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="Product2" type="tns:productAnnotated"/>

<xs:complexType name="productAnnotated">

<xs:sequence>

<xs:element name="VERSION"

type="xs:string" default="1.0" minOccurs="0"/>

<xs:element name="mfrDate"

type="xs:dateTime" minOccurs="0"/>

<xs:element name="name"

type="xs:string" minOccurs="0"/>

</xs:sequence>

</xs:complexType>

</xs:schema>

Let’s take a moment to see how each of the annotations contributed to the creation of this schema. First, the XMLRootElement annotation defined the namespace for the root element. But that’s not actually enough to get the namespace declared across the com-plex type, as that annotation applies only to the root element. So you must repeat that namespace in the XMLType annotation in order to tell schemagen that both the element and the complex type are in the same namespace. This results in the single file that you see here. Had you left off the XMLType annotation, schemagen would have made a con-servative guess and left the complex type with no namespace. It then would have been forced to put it in a separate schema file and import the type into the schema defining the element.

Notice that using the XMLElement field-level modifier you get to retain your static field in the schema definition. But you’re just overriding the fact that it would have been ignored; there is no concept of “static” in schema you can retain.

Using SchemaOutputResolver

The second way to create a schema based on a Java class is not to use the command-line tool, but rather to write Java code that does it for you. This has the advantage of being usable at runtime within a larger program. Example 2-20 is the listing that writes out a schema for your ProductAnnotated.java file.

Example 2-20. Java program to generate a schema at runtime package com.soacookbook.ch02.schemagen;

* Generates a schema from a Java class.

*/

public class SchemaMaker {

SchemaOutputResolver resolver;

//run the show

public static void main(String...arg){

try {

* Creates an instance of SchemaMaker with defaults.

*/

public SchemaMaker() {

resolver = new MySchemaOutputResolver(".", "MySchema.xsd");

}

public void execute(Class...classes) throws JAXBException, IOException {

JAXBContext context = JAXBContext.newInstance(classes);

context.generateSchema(resolver);

out.println("All done.");

} } /**

* Extends the resolver.

*/

class MySchemaOutputResolver extends SchemaOutputResolver {

private File output;

public MySchemaOutputResolver(String dir, String fileName){

output = new File(dir, fileName);

}

public Result createOutput(String namespaceUri, String suggestedFileName) throws IOException {

return new StreamResult(output);

} }

Running this file produces an identical result to the one achieved by generating the schema with schemagen and JAXB annotations earlier, but with the benefit of being able to use it more flexibly at runtime.

在文檔中 Java SOA Cookbook (頁 101-106)