| ||
IBM home | Products & services | Support & downloads | My account |
|
XML and Java technologies: Data binding Part 3: JiBX architecture | ||||
Tests in Part 2 showed JiBX delivers great performance --
here's how!
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
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
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
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:
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 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 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 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 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
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
Most data binding frameworks support mapping this document to instances of classes along the lines of: Listing 2. Classes matching document structure
Most frameworks also allow you to change the names of the fields, to
use What most frameworks will not allow you to do is to bring the
values within the
Nor can you map the XML to a pair of classes like these: Listing 4. Classes with different structure
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 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!
|
About IBM | Privacy | Terms of use | Contact |