Level: Intermediate Dennis Sosnoski (dms@sosnoski.com), President, Sosnoski Software Solutions, Inc.
01 Apr 2003 Enterprise Java technology expert Dennis Sosnoski gives a guided tour of his JiBX framework for XML data binding in Java applications. After introducing the current frameworks in Part 1 and comparing performance in Part 2, he now delves into the details of the JiBX design that led to both great performance and extreme flexibility for mapping between XML and Java objects. How does JiBX do it? The keys are in the internal structure...
Part 1 of this series gives background on why you'd want to use data binding for XML,
along with an overview of the available Java frameworks for data binding. Part 2
shows the performance of several frameworks on some sample documents. Here in
Part 3 you'll find out the details of the new JiBX framework that delivers such
great test scores. I'll start by recapping some basics from the introduction to
JiBX in Part 2.
JiBX originated as my experiment to explore the performance limits of
XML data binding in Java technology. As it progressed beyond the experimental stage, I
decided to also make it a test bed for trying out what I call a Java-centric approach to data binding -- as opposed to the XML-centric approach used in
generating an object model from an XML grammar. Because of these choices,
JiBX deliberately differs from the established data binding frameworks in several respects.
At the core of these differences is the parsing technology that's used when
unmarshalling documents. The established frameworks are all based on the
widely used SAX2 push parser API. JiBX instead uses a newer pull parser API
that provides a more natural interface for dealing with the sequence of elements
in a document. Because of this different API, JiBX is able to use relatively
simple code to handle unmarshalling.
This simpler unmarshalling code is the basis for the second major
difference between JiBX and the other frameworks. The other frameworks either
generate their own data classes -- which you're then required to use in your
application -- or use a technique called reflection that provides runtime
access to data indirectly, using the class information known to the Java
Virtual Machine (JVM).
JiBX instead uses code that it adds to your
existing classes, working directly with the binary class files generated by
a Java compiler. This gives the speed of direct access to data while keeping
the flexibility of working with classes that you control.
The third JiBX difference goes along with this use of your existing classes.
JiBX reads a binding definition file to control how the code it adds to your
classes converts instances to and from XML. JiBX isn't the only data binding
framework that supports this type of approach -- Castor, for one, also handles this
-- but JiBX goes further than the alternatives in allowing you to change the
relationship between your classes and the XML document.
I'll give details on each of these points in the remainder of this
article. Even if you're planning to use an alternative framework for your
projects, you'll find some solid food for thought in going through
this description of JiBX.
Pulling for performance
The biggest single difference between JiBX and the other data binding frameworks
is in how they handle an input XML document. Most XML parsers for the Java language
implement the SAX2 API for use by the application program. This
API has been refined over a period of years and is supported by many
different parser implementations, including the most fully-featured parsers available today.
 |
Data binding dictionary
Here's a mini-dictionary of some terms I'll use in this article:
Marshalling is the process of generating an XML representation for an
object in memory, potentially including objects linked from the original object.
Unmarshalling is the reverse process of marshalling, building an object
(and potentially a graph of linked objects) in memory from an XML representation.
Mapping is the set of rules used for the marshalling and
unmarshalling of objects to and from XML documents. Data binding approaches using
code generation from a grammar normally
have implicit mappings built into the constructed objects, but JiBX (along with
a few other frameworks) instead uses an explicit binding definition to control
the mapping.
Grammar is a set of rules defining the structure of a family of XML
documents. One type of grammar is the Document Type Definition (DTD) format defined by the
XML specification, another increasingly common one is the W3C XML Schema (Schema) format.
|
|
Despite it's widespread usage, the SAX2 API has some serious drawbacks for
many types of XML processing. These result from the style of
interface implemented by SAX2 -- a push parsing approach. In push parsing, you define methods
within your code that implement interfaces defined by the parser API. You then
pass these methods (in the form of a handler) to the parser, along with a
document to be parsed. The
parser goes through the text of the document and interprets it according to XML
rules, calling your handler methods to report the document components
(elements, character data content, and so forth.) as it finds them in the text. Once
the parser has completely processed the input document, it returns control
back to your application -- but in the meantime the parser is in control.
The problem with this approach is that it requires your
handler code to always know where the parser is in the document, and to
interpret the components appropriately. At the most basic level, an element
containing a simple text value is reported as three (or more) separate
components: first the start tag, then the text (which may be in multiple
pieces), and finally the end tag. Your handler generally needs to accumulate
text until the end tag is reported, and then do something with the text. The "something" it does may be affected by other items, such as the
attributes of the start tag, so that's more information that needs to be
held. The net result is that handler code for push parsing
tends to involve a lot of String matching on
element names in chained if statements (or the equivalent using a
java.util.Hashtable). This is messy
to write and messy to maintain, not to mention inefficient.
Pull parsing turns parse event reporting around. Instead
of the parser calling methods in your handler to report document
components, you call the parser to get each component in turn -- the
parser becomes essentially an iterator for moving through the components
of a document. When you write code using this approach, the state
information is actually inherent in the code. Take the case of an
element that contains text: You can write your code with the knowledge
that the element you're processing has text content, and just process it
directly -- effectively one call to get the start tag, one call to get
the content, and a third to get the end tag.
Better yet, you can write a method that handles any element
that contains a text value -- just call the method with the expected element name,
and it can return the text content (or an error, if the expected element is
missing). This is especially convenient for building objects from XML (which
is what unmarshalling is all about). Most XML documents use fixed ordering of
child elements, so processing the children of a particular element becomes as
simple as just making one call after another to this method. This is both
simpler and faster than a SAX2-style handler approach.
Staying in context
JiBX extensively uses this technique of combining multiple parser operations in a single method.
It wraps an internal pull parser
interface in an unmarshalling context class that defines a variety of
element and attribute access methods. These methods handle both parsing and data
conversions to primitive types, along with specialized types of operations such
as tracking objects with unique identifiers. Here are some examples of these methods:
-
parsePastStartTag(ns, name) --
Parses past the start of element, which must be the next parse component seen other
than character data. Leaves the parse positioned following the start tag.
-
attributeText(ns, name) -- Gets text value of
required attribute from current start tag.
-
attributeInt(ns, name, dflt) -- Gets
int value of optional attribute from current start
tag (returning default value if attribute is not present).
-
parseContentText(ns, name) -- Parses past the end of
the current element, which must have only character data content. Returns the content.
-
parseElementText(ns, name) -- Parses the next element,
which must have only character data content. Returns the content.
Wrapping the parser within an unmarshalling context allows a wide variety
of access methods to be defined with a minimum of code. It also provides
isolation from the actual parser being used. Because of this, the generated
code that gets added to your classes is not specific to a particular parser, or
even to a particular type of parser. This may be an important concern for the
future. Right now the
unmarshalling context is coded to use a parser that implements the XMLPull
interface, an informal standard defined by some of the leading developers in
the area of pull parsing. In the future, there's likely to be a Java technology
standard for pull parsers. When that standard becomes available, JiBX will be
able to use the standard with only minor changes to the unmarshalling context
code.
JiBX uses a second type of context, the marshalling context, for
writing an XML document when marshalling. Just as the unmarshalling context
wraps the parser and provides a variety of specialized convenience methods for
unmarshalling, the marshalling context wraps an output stream and provides
its own set of convenience methods. These methods provide a component-at-a-time
interface for constructing the output document. Here's a simplified list of
basic methods:
-
startTag(nsi, name) --
Generates start tag without attributes.
-
startTagAttributes(nsi, name) -- Generates start
tag with attributes to be added.
-
attribute(nsi, name, value) -- Adds attribute to
current start tag (value may be String or primitive
type).
-
closeStartEmpty() -- Closes start tag with no
content (empty element).
-
closeStartContent() -- Closes start tag with
content to follow.
-
textContent(value) -- Adds character data content
to current element (String or primitive).
-
endTag(nsi, name) -- Generates end tag for
element.
This interface for generating XML is simpler than those used by many
other frameworks. It's designed primarily for use by code that's added by the JiBX
framework rather than written by hand, so it avoids state checks and similar
safeguards. It does properly handle escaping special characters as
entities in text, but other than that it's up to the calling program to use
the interface properly.
In the form shown here, this interface works with namespace indices. The
marshalling context keeps an internal array of prefixes for namespaces,
so looking up the prefix when generating output is just a matter of indexing
into this array (as opposed to requiring a mapping operation, which would be needed if
actual namespace URIs were passed). This approach is a compromise that
simplifies handling without throwing away flexibility.
An earlier version of the interface required static construction of qualified
names (including a namespace prefix, if needed) for elements and attributes.
This worked well for generating text output, but would have been difficult to
convert to other forms of output (such as a parse event stream for building a
DOM representation of the document).The newer form of the interface makes conversion much easier.
Java-centric versus XML-centric
Most of the data binding frameworks for Java language programs are
focused on code generation from W3C XML Schema grammars. Using this
approach, you start with a Schema grammar for the documents to be
processed, then use a program that's part of the data binding framework
to generate the Java language source code for a set of classes (the
object model) that correspond to the Schema. This works well for many
applications, especially those where the Schema grammars are defined in
advance and not subject to change during the course of the project. For
these types of applications, the object model constructed from a Schema
can provide a very fast way to start working with documents.
The main drawback to the generated object model approach is that it ties
your code directly to the structure of the XML document. Because of this, I call
it an XML-centric approach. With a generated object model, your application
code is forced to use an interface that reflects the XML structure rather than
the structure the application may want to impose on the data in a document. This
means there's no isolation between the XML and your application -- if the XML
document structure (the Schema) changes in any significant respect, you'll need
to regenerate the object model and change your application code to match.
A few data binding frameworks (including JiBX) have implemented an alternative approach,
generally called mapped binding. This is what I call a Java-centric
approach. It works with classes you define in your application rather
than forcing the use of a set of generated classes. Although this is convenient
for developers, it makes the framework's job more complex. The framework somehow
needs to get data into and out of the application classes in a consistent manner,
without forcing the application classes to follow a rigid model. This is where
another of the differences between JiBX and the other data binding frameworks
enters in.
Reflecting on performance
When a framework needs to get data into and out of a variety of Java language
classes, the usual way of doing this is by using reflection. This is
the approach taken by Castor, for instance, in its mapped binding support. is a way of accessing information about a Java language class at runtime. It can
be used to access fields and methods in instances of a class, providing a way of
dynamically hooking together classes at runtime without the need for any source
code links between the classes.
Reflection is a great basis for many types of
frameworks that work with Java classes, but it does have some drawbacks when used
for data binding. For one thing, it generally requires you to use public fields
or methods in your classes to allow reflection to operate properly. This means
you need to provide access to internal state information as part of your public
API, when really it should only be accessible by the binding framework. Reflection
also suffers a performance disadvantage when compared to calling a method
or accessing a field directly in compiled code.
Because of these limitations I wanted to avoid using reflection for data
binding in JiBX. The developers of the Java Data Objects (JDO) specification
had faced similar problems in moving data between Java language objects and
databases. In their case, they avoided using reflection by instead working
directly with class files, adding code to the classes generated by the compiler
in order to provide direct access for the framework. I chose to use this same
approach for JiBX.
Enhancing class files
The JDO team came up with a great term to describe the process of munging
(see Resources)
the class files generated by a compiler: "Byte code enhancement." Sounds
much better than saying your class files are being "mangled" or "implanted",
doesn't it? I don't think I can improve on the term, so I'm just shamelessly
expropriating it for use by the JiBX framework as well.
The actual class file modifications are done by a JiBX component called the
binding compiler. The binding compiler is executed at program assembly
time -- after you've compiled your Java language source code to class files, but
before packaging or executing the class files. The binding compiler reads one or
more binding definition documents (see Defining bindings). For each
class included in any of the bindings it adds the appropriate marshalling or
unmarshalling code.
In general, you get a pair of added methods in your bound class files for each
marshalling or unmarshalling binding that includes that class. There are some
exceptions to this -- if a class is treated exactly the same way in multiple
bindings those bindings reuse a single set of methods, and some types of
binding operations may require more or fewer added methods. Four methods per binding
(two for marshalling, two for unmarshalling) is about the average, though.
The binding compiler also generates some added support classes that go
along with the added methods. Basically, one small helper class is
added for each class that's included in any of the bindings, along with a separate class per binding.
In the special case of unmarshalling forward references to objects identified by
an ID value, the equivalent of a small inner class is also generated to fill
in the value of the forward-referenced object once it's been unmarshalled.
Normally the total number of added classes and the total size of the added
code is fairly small. The sample you can download from the JiBX site includes an
example based on the Part 2 test code (see Resources). This generates 6 added classes and 23
added methods, with a total added size of 9 KB, to handle a binding
involving 5 classes. This sounds like a lot -- but compare this with other
alternatives based on code generation from a grammar in Figure 1.
This shows the total code size for JiBX, including both base classes and the
added binding code, compared with the total generated code size for the other
frameworks. Only Quick is able to do better than JiBX in code size.
Figure 1. Binding code comparison
Defining bindings
The JiBX binding compiler described in the last section works from
binding definitions that you supply. Binding definitions are themselves XML documents,
with a DTD grammar included in the download. Unlike most other data binding
frameworks, JiBX supports using any number of binding definitions in combination.
You can, for instance, unmarshal a document using one binding, make
changes, then marshal the data objects to an output document using a different
binding.
A binding definition lets you control such basic details as whether a
particular simple property of a class (such as a primitive value, or a
String) is mapped to an element or to an attribute in
the XML document, and the name used for that element or attribute. You can also
tell JiBX how to access that property -- either as a field (which can have any
access qualifier, including private) or as a JavaBean-style property with get
and set methods. You can even specify values as optional, and define formatters
for converting simple values to and from text.
However, JiBX goes beyond this simple form of mapping. Other data binding
frameworks generally force each XML element with complex content (attributes
or child elements) to be mapped to and from a distinct object, so that your
object model directly matches the layout of your XML documents. With JiBX
you can be more flexible: Child elements can be defined using a subset of the
properties of an object, and properties of a referenced object can be included
directly as simple child elements or attributes of the current element. This lets you
make many types of changes to either your class structure or your XML document
structure without needing to change the other to match.
Structure mapping
I call this type of mapping, where XML elements do not correspond directly to
Java language objects, a structure mapping. For a simple example of how this
works with JiBX, consider the following XML document:
Listing 1. Sample XML document
<customer>
<name>
<first-name>John</first-name>
<last-name>Smith</last-name>
</name>
<street1>12345 Happy Lane</street1>
<city>Plunk</city>
<state>WA</state>
<zip>98059</zip>
<phone>888.555.1234</phone>
</customer>
|
Most data binding frameworks support mapping this document to
instances of classes along the lines of:
Listing 2. Classes matching document structure
public class Customer {
public Name name;
public String street1;
public String city;
public String state;
public String zip;
public String phone;
}
public class Name {
public String firstName;
public String lastName;
}
|
Most frameworks also allow you to change the names of the fields,
to use customerName instead of name, and
first rather than firstName, for example. Some
frameworks also use get/set methods rather than the public fields shown
here, but the principle remains the same.
What most frameworks will not allow you to do is to bring
the values within the name element into the object that corresponds
to the customer element. In other words, you can't map the XML to this
class:
Listing 3. Same data in a single class
public class Customer {
public String firstName;
public String lastName;
public String street1;
public String city;
public String state;
public String zip;
public String phone;
}
|
Nor can you map the XML to a pair of classes like these:
Listing 4. Classes with different structure
public class Customer {
public String firstName;
public String lastName;
public Address address;
public String phone;
}
public class Address {
public String street1;
public String city;
public String state;
public String zip;
}
|
JiBX does allow you to do this type of mapping. This means
the structure of your objects is not tied to the structure of the XML -- you
can restructure your object classes without needing to change the XML format
used for external data transfer. I'll give you specifics on this (including
actual mapping files for the above pair of examples) in the next article in
this series.
Conclusions
The basic architecture of JiBX is very different from those of other XML data binding
frameworks for Java applications. This leads to both advantages and drawbacks
when comparing JiBX with these other frameworks. On the plus side, JiBX gains
the advantages of very fast operation, a compact runtime, and greater isolation
between XML document formats and Java language object structures. On the minus
side, it's based on a relatively unproven parser technology and does not
support the validation flexibility provided by some of the alternatives
(especially JAXB).
Some of the other differences between JiBX and alternative frameworks are
less clear-cut. For instance, JiBX's technique of class file enhancement offers
the advantage of keeping your source code clean -- the binding code is added
after you compile, so you never need to deal with it directly -- but at the
costs of an added step in the build process and potential confusion in tracking
problems in your code accessed during marshalling or unmarshalling. For some
users and applications, one or another of these factors will be more important
than others.
Part 4 of this series will dive into the details of using JiBX in your
applications. Once I've shown you how to use it I'll also return to this issue
of what I see as JiBX's weaker points, and go on to suggest some possible ways
in which these can be strengthened in the future. Check out Part
4 to get the rest of the JiBX story!
Resources
- Learn more about the new JiBX framework for mapped bindings.
-
Part 1 of this series on data binding provides background on why you'd want to use data binding for XML, along with an overview of the available Java frameworks for data binding. Part 2 gives performance comparisons between the data binding frameworks, including the new JiBX framework (developerWorks, January 2003).
- If you need background on XML, try the developerWorks "Introduction to XML tutorial" (August 2002).
- Review the author's previous developerWorks articles covering performance (September 2001) and usage (February 2002) comparisons for Java XML document models.
- Go to the source(Forge) to find out about the widely used SAX2 parser API for push parsing XML.
- Learn about the fast and flexible XmlPull parser API developed by some of the leading pull parser researchers.
- Track the progress of the Java Specification Request for a Streaming API for XML (StAX).
- Read Brett McLaughlin's overview of Quick in "Converting between Java objects and XML with Quick," which shows you how to use this framework to quickly and painlessly turn your Java data into XML documents, without the class generation semantics required by other data binding frameworks (developerWorks, August 2002).
- Find out more about the Java Architecture for XML Binding (JAXB), the evolving standard for Java Platform data binding.
- Take a closer look at the Castor framework, which supports both mapped and generated bindings.
- The Quick framework is based on a
series of development efforts that predate both the Java Platform and XML. It provides
an extremely flexible framework for working with XML on the Java Platform.
- Explore the details of Zeus, which (like Quick) generates code based on DTD descriptions of XML documents, but is simpler to use -- and more limited -- than Quick.
About the author  | 
|  | Dennis Sosnoski (dms@sosnoski.com) is the founder and lead consultant of the
Seattle-area Java consulting company Sosnoski Software Solutions, Inc., specialists in J2EE, XML, and Web services support. Dennis's professional software development experience spans over 30 years, with the last several years focused on server-side Java technologies. He's a frequent speaker on XML in Java and J2EE technologies at conferences nationwide, and chairs the Seattle Java-XML SIG. |
Rate this page
|