We’ve said that our ultimate goal in this chapter is automated binding of XML to Java classes. Now we’ll discuss the standard Java API for XML Binding, JAXB. (This should not be confused with JAXP, the parser API.) JAXB is a standard extension that is bundled with Java 6 and later. With JAXB, the developer does not need to create any fragile parsing code. An XML schema or Java code can be used as the starting point for transforming XML to Java and back. (“Schema first” and “code first” are both supported.) With JAXB, you can either mark up your Java classes with simple annotations that map (bind) them to XML or start with an XML schema and generate plain Java classes (POJOs) with the necessary annotations included. You can even derive an XML schema from your Java classes to use as a starting point or contract with non-Java systems.
At runtime, JAXB can read an XML document and parse it into the
model that you have defined or you can go the other way, populating your
object model and then writing it out to XML. In both cases, JAXB can
validate the data to make sure it matches a schema. This may sound like
the DOM interface, but in this case we’re not using generic classes—we’re using our own model. In this
section, we’ll reuse the class model that we created for the SAX example
with our zooinventory.xml file. We’ll use the
familiar Inventory
, Animal
, and FoodRecipe
classes directly, but this time
you’ll see that we’ll be more focused on the schema and names and less on
the parsing machinery.
JAXB gives us a great deal of flexibility in mapping our Java classes to XML elements and there are a lot of special cases. But if we accept most of the default behavior for our model, we can get started with very little work. Let’s start by taking our zoo inventory classes and adding the necessary annotations to allow JAXB to bind it to XML:
@XmlRootElement
public
class
Inventory
{
public
List
<
Animal
>
animal
=
new
ArrayList
<>();
}
Well, that was easy! Yes, in fact as we hinted at the beginning of
the chapter, adding just the @XmlRootElement
annotation to the “top level” or root class of our model will yield
nearly the same XML that we used before. To generate the XML, we’ll use
the following test harness:
import
javax.xml.bind.JAXBContext
;
import
javax.xml.bind.JAXBException
;
import
javax.xml.bind.Marshaller
;
public
class
TestJAXBMarshall
{
public
static
void
main
(
String
[]
args
)
throws
JAXBException
{
Inventory
inventory
=
new
Inventory
();
FoodRecipe
recipe
=
new
FoodRecipe
();
recipe
.
name
=
"Gorilla Chow"
;
recipe
.
ingredient
.
addAll
(
Arrays
.
asList
(
"leaves"
,
"insects"
,
"fruit"
)
);
Animal
animal
=
new
Animal
(
Animal
.
AnimalClass
.
mammal
,
"Song Fang"
,
"Giant Panda"
,
"China"
,
"Bamboo"
,
"Friendly"
,
45.0
,
recipe
);
inventory
.
animal
.
add
(
animal
);
marshall
(
inventory
);
}
public
static
void
marshall
(
Object
jaxbObject
)
throws
JAXBException
{
JAXBContext
context
=
JAXBContext
.
newInstance
(
jaxbObject
.
getClass
()
);
Marshaller
marshaller
=
context
.
createMarshaller
();
marshaller
.
setProperty
(
Marshaller
.
JAXB_FORMATTED_OUTPUT
,
Boolean
.
TRUE
);
marshaller
.
marshal
(
jaxbObject
,
System
.
out
);
}
}
We’ve taken the liberty of adding some constructors to shorten the
code for creating the model, but it doesn’t change the behavior here.
It’s just the four lines of our marshall()
method that actually use JAXB to
write out the XML. We first create a JAXBContext
, passing in
the class type to be marshalled. We’ve made our marshall()
method somewhat reusable by getting
the class type from the object passed in. However, it’s sometimes
necessary to pass in additional classes to the newInstance()
method in order for JAXB to be
aware of all of the bound classes that may be needed. In that case, we’d
simpy pass more class types to the newInstance()
method (it accepts a variable
argument list with any number of arguments—of class types). We then
create a Marshaller
from the context
and, for our purposes, set a flag indicating that we would like nice,
human-readable output (the default output is one long line of XML).
Finally, we tell the marshaller to send our object to System.out
.
The output looks like this:
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
standalone
=
"yes"
?>
<
inventory
>
<
animal
>
<
animalClass
>
mammal
</
animalClass
>
<
name
>
Song
Fang
</
name
>
<
species
>
Giant
Panda
</
species
>
<
habitat
>
China
</
habitat
>
<
food
>
Bamboo
</
food
>
<
temperament
>
Friendly
</
temperament
>
<
weight
>
45.0
</
weight
>
</
animal
>
<
animal
>
<
animalClass
>
mammal
</
animalClass
>
<
name
>
Cocoa
</
name
>
<
species
>
Gorilla
</
species
>
<
habitat
>
Ceneral
Africa
</
habitat
>
<
temperament
>
Know
-
it
-
all
</
temperament
>
<
weight
>
45.0
</
weight
>
<
foodRecipe
>
<
name
>
Gorilla
Chow
</
name
>
<
ingredient
>
fruit
</
ingredient
>
<
ingredient
>
shoots
</
ingredient
>
<
ingredient
>
leaves
</
ingredient
>
</
foodRecipe
>
</
animal
>
</
inventory
>
As we said, it’s almost identical to the XML we worked with
earlier. Admittedly, we chose to create our XML using the same (common)
conventions that JAXB uses, so it’s not entirely magic. The first thing
to notice is that JAXB automatically mapped our class names to lowercase
XML element names (e.g., class Animal
to <animal>
). If we had used
JavaBeans-style getter methods instead of public fields, the same would
be true; for example, a getSpecies()
method would produce a default element name of species
.
If we wanted to map our class names and property names to
completely different XML names, we could easily accomplish that using
the name attribute of the @XmlRootElement
and
@XmlElement
annotations. For example, we can call our Animal
“creature” and rename temperament
to “personality” like so:
@XmlRootElement
(
name
=
"creature"
)
public
class
Animal
{
...
@XmlElement
(
name
=
"personality"
)
public
String
temperament
;
The real difference between our generated XML and our earlier
sample is that our animalClass
attribute is not acting like an attribute. By default, is has been
mapped to an element, like the other properties of Animal
. We can rectify that with another
annotation, @XmlAttribute
:
public
class
Animal
{
@XmlAttribute
public
AnimalClass
animalClass
;
...
// Produces...
<
inventory
>
<
animal
animalClass
=
"mammal"
>
<
name
>
Song
Fang
</
name
>
Also note that JAXB has shown the food
element in the first animal and the
foodRecipe
in the second. JAXB will
ignore a field or property that is null
(as is the case here) unless you specify
that the property is “nillable” using @XmlElement(nillable=true)
. That behavior
automatically supported the alternation between our two
properties.
There are many additional annotations that provide support for mapping Java classes, fields, and properties to other features of XML. Table 24-6 attempts to provide a concise description of what each annotation is used for. Some of the usages get a little complex,so you may want to refer to the Javadoc for more details.
Table 24-6. JAXB Annotations
Annotation | Description |
---|---|
@XmlAccessorOrder | Used on a package or class to set alphabetic ordering
of marshalled fields and properties. (The default ordering is
undefined.) See @XmlType to
specify the ordering yourself. As a reminder: package-level
annotations in Java are placed on a (lonely) package statement
in a special file named package-info.java within the
corresponding package structure. (See Annotations.) |
@XmlAccessorType | Used on a package or class to specify whether fields
and properties are marshalled by default. You can choose: only
fields, only properties (getters/setters), none (only those
annotated by the user), or all public fields and properties.
See @XmlTransient to
exclude items. |
@XmlAnyAttribute | Designates a Java Map object to receive any unbound
XML attribute name-value pairs for an entity (i.e., the
Map will collect any
leftover attributes for which no corresponding property or
field can be found). |
@XmlAnyElement | Designates a Java List or Array object to receive any unbound
XML elements for an entity (i.e., the List will accumulate any leftover
elements for which no corresponding property or field can be
found). |
@XmlAttachmentRef | Designates a java.activation.DataHandler object
to handle an XML MIME attachment. |
@XmlAttribute | Binds a Java field or property to an XML attribute. The
name attribute can be used
to specify an XML attribute name that is different from the
name of the field or property. Use the required attribute to specify
whether the attribute is required. |
@XmlElement | Binds a a Java field or property to an XML element. The
name attribute can be used
to specify an XML element name different from the name of the
field or property. Use the required attribute to specify
whether the element is required. |
@XmlElements | Used on a Java collection to specify distinct element
names for contained items based on their Java type. Holds a
list of @XmlElement
annotations with name and
type attributes that
explicitly map Java types in the collection to XML element
names (e.g., in our example, inventory contains animal elements because our List
property is named “animal”). If we chose to have subclasses of
Animal in our inventory
collection, we could map them to XML element names such as
gorilla and lemur . See @XmlElementRef . |
@XmlElementRef | Similar to @XmlElements , used to generate
individualized names for Java types in a collection. However,
instead of the names for each type being specified directly,
they are determined at runtime by the individual types’ Java
type bindings (e.g., in our example, inventory contains animal elements because our List is
named “animal”). Using @XmlElementRef , we could subclass
Animal and have our
inventory contain elements
like gorilla and lemur , with the names determined by
@XmlRootElement annotations
on the respective subclasses. See important class binding info
in @XmlElementRefs . |
@XmlElementRefs | Used on a Java collection to provide a list of @XmlElementRef annotations with
type attributes that
explicitly specify the Java types that may appear in the
collection. The effect is the same as using a simple @XmlElementRef on the collection,
but we actively tell JAXB the class names that have bindings.
If not supplied in this way, we have to provide the full list
of bound classes to the JAXBContext newInstance() method in order for
them to be recognized. |
@XmlElementWrapper | Used on a Java collection to cause the sequence of XML
elements to be wrapped in the specified element instead of
appearing directly inline in the XML (e.g., our animal elements appear directly in
inventory ). Using this
annotation, we could nest them all within a new animals element. |
@XmlEnum | Binds a Java Enum to XML and allows @XmlEnum Values annotations to be
used to map the enum values for XML if required. |
@XmlEnumValue | Binds an individual Java Enum value to a string to be
used in the XML (e.g., our mammal enum value could be mapped to
“mammalia”). |
@XmlID | Supports referential integrity by designating a Java
property or field of a class as being the XML ID attribute (a unique key) for the
XML element within the document. |
@XmlIDREF | Supports referential integrity by designating a Java
property or field as an idref attribute pointing to an
element with an @XmlID . The
annotated property or field must contain an instance of a Java
type containing an @XmlID
annotation. When marshalled, the attribute name will be the
property name and the value will be the contained XML ID
value. |
@XmlInlineBinaryData | Bind a Java byte array to receive base64 binary data encoded in the XML. |
@XmlList | Used on a Java collection to map items to a single simple content element with a whitespace-separated list of values instead of a series of elements. |
@XmlMimeType | Used with a Java Image or Source type to specify a MIME type for XML base64-encoded binary data bound to it. |
@XmlMixed | Binds a Java object collection to XML “mixed content”
(i.e., XML containing both text and element tags within it).
Text will be added to the collection as String objects interleaved with the
usual Java types representing the other elements. |
@XmlRootElement | Bind a Java class to an XML element optionally provide a name. This is the minimum annotation required on your class to make it possible to marshal it to XML and back. |
@XmlElementDecl | Used in binding XML schema elements to methods in Java object factories created in some code generation scenarios. |
@XmlRegistry | Used with @XmlElementDecl in designating Java
object factories used in some code generation
scenarios. |
@XmlSchema | Binds a Java package to a default XML namespace. |
@XmlNs | Used with @XmlSchema
to bind a Java package to one or more XML namespace
prefixes. |
@XmlSchemaType | Used on a Java property, field, or package. Specifies a Java type to be used for a standard XML schema built-in types, such as date or a numeric type. |
@XmlSchemaTypes | Used on a Java package. Holds a list of @XmlSchemaType annotations mapping
Java types to built-in XML schema types. |
@XmlTransient | Designates that a Java property or field should not be
marshaled to the XML. This can be used in conjunction with
defaults that marshal all properties or fields to exclude
individual items. See @XmlAccessorType . |
@XmlType | Binds a Java class to an XML schema type. Additionally,
the propOrder attribute may
be used to explicitly list the order in which elements are
marshalled to XML. |
@XmlValue | Designates that a Java property or field contains the
“simple” XML content for the Java type; that is, instead of
marshalling the class as an XML element containing a nested
element for the property, the value of the annotated property
will appear directly as the content. The Java type may have
only one property designated as @XmlValue . |
Creating our object model from XML just requires a few lines to
create an Unmarshaller
from our
JAXBContext
and a cast to the Java
type of our root element:
JAXBContext
context
=
JAXBContext
.
newInstance
(
Inventory
.
class
);
Unmarshaller
unmarshaller
=
context
.
createUnmarshaller
();
Inventory
inventory
=
(
Inventory
)
unmarshaller
.
unmarshal
(
new
File
(
"zooinventory.xml"
)
);
The Unmarshaller
class
has a setValidating()
method like the SAXParser
, but it
is deprecated. Instead, we could use the setSchema()
method to
set an XML Schema representation if we want validation as part of the
parsing process. Alternately, we could just validate the schema
separately. See XML Schema.
If you are starting with an XML Schema
(xsd file), you can generate annotated Java classes
from the schema using the JAXB xjc
command-line tool
that comes with the JDK.
xjc
zooinventory
.
xsd
// Output
parsing
a
schema
...
compiling
a
schema
...
generated
/
Animal
.
java
generated
/
FoodRecipe
.
java
generated
/
Inventory
.
java
generated
/
ObjectFactory
.
java
By default, the output is placed in the default package in a
directory named generated. You can control the
package name with the -p
switch and the
directory with -d
. See the xjc
documentation for more options.
Studying the generated classes will give you some hints as to how
many annotations are used, although xjc
is a little more verbose than it has to
be. Also note that xjc
produces a
class called ObjectFactory
that
contains factory methods for each type, such as createInventory()
and createAnimal()
. If you look at these methods,
you’ll see that they really just call new
on the plain Java objects and they seem
superfluous. The ObjectFactory
is
mainly there for legacy reasons. In ealier versions of JAXB, before
annotations, the generated classes were not as simple to construct.
Additionally, the ObjectFactory
contains a helper method to create a JAXBElement
type, which may be useful in
special situations. For the most part, you can ignore these.
You can also generate an XML Schema directly from your
annotated Java classes using the JAXB XML Schema binding generator:
schemagen
. The schemagen
command-line tool comes with the
JDK. It can generate a schema starting with Java source or class files.
Use the -classpath
argument to
specify the location of the classes or source files and then provide the
name of the root class in your hierarchy:
schemagen
-
classpath
.
Inventory
Having worked our way through the options for bridging XML to Java, we’ll now turn our attention to transformations on XML itself with XSL, the styling language for XML.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.