After a hiatus of three months, this article completes the three-part series on the XMLBeans package. Simply put, XMLBeans can transform a JavaBean in memory into an XML document, or can transform an XML document (of a particular form) into a running JavaBean. Figure 1 shows an extremely simplified schematic of the concept.
If you've experimented with XML and JavaBeans in this series, you'll have noticed that the
XMLBeanWriter class we created in Part 2 is capable of creating XMLBeans XML files that the
XMLBeanReader from Part 1 can't read. Some readers have also noted that the XMLBeans file format is somewhat complicated, and that controlling which properties are encoded in XML and which properties are ignored requires a lot of tedious coding. This month, we'll solve these problems by restructuring the
XMLBeanWriter classes. In the process, we'll simplify the file format while maintaining backward compatibility. Perhaps most interestingly, we'll simplify and generalize controlling XMLBeans properties by integrating XMLBeans with the core
This article covers some intermediate-to-advanced JavaBeans topics, so you may need to do some background reading to get up to speed. The links to the articles described here appear in the Resources section.
TEXTBOX_HEAD: XML JavaBeans: Read the whole series!
First, I assume you've read and understand the first two articles (Parts 1 and 2) in this series. If you haven't read these articles, this month's article may not make much sense to you.
If you're new to XML, you might consider reading "XML for the absolute beginner," (JavaWorld April 1999). This article discusses JavaBeans property descriptors, property editor classes, and
BeanInfo objects, topics previously covered in "The trick to controlling bean customization," and "Soup up your Java classes with customization." Once you have a passing understanding of these subjects, you'll be ready to tackle this final installment of the XML JavaBeans series.
As always, I shortened the code listings in this article by putting the code section under discussion into a scrolling list box. Since some readers dislike this convention, each code listing's caption includes a hyperlink to a printable HTML file. You can pop open a new browser and scroll this complete listing, if you'd like, or you can print the file and follow along on paper.
I find software to be in some ways like art: in practically every instance, as soon as I've completed a project, I'm aware of my work's weaknesses and how it could have been done better. The code we're going to discuss is a second attempt at creating an interface to read and write JavaBeans objects as XML documents. We won't spend a lot of time discussing the weaknesses of the previous design, but you'll see as we go along that the new implementation is cleaner and more flexible than the original.
This article introduces four major improvements to the XMLBeans package. The first involves general restructuring and simplification of the code, taking advantage of the concept that the value of any JavaBean property may be a primitive value, an object, or another JavaBean. The second improvement is an expansion of the customization of how XMLBeans read and write their properties in XML (or, more accurately, as DOM trees). Third, we give the programmer control over properties' visibility and behavior by integrating the XMLBeans package with the core
java.beans package. And finally, we provide an alternate, less restrictive (and less well-defined) format for the XML produced and read by the package.
We'll start by going over the general restructuring improvement.
A value by any other name
The original XMLBeans implementation drew a distinction between JavaBeans properties that are "values" and those that are "beans." Value types were either Java primitive types (
double) or any object type that is not primitive but can be constructed from a string (
java.net.URL, for example). Bean types were explicitly encoded in XML as
<JavaBean CLASS="<i>classname</i>">, and properties whose values were beans were processed differently from those that were not.
After writing the first version of the XMLBeans package, it became clear that there's not much of a difference between a property type that's a primitive value, one that's an object, or one that's a JavaBean. Primitive types require some special treatment, that's true, but the basic process for reading a JavaBean in terms of its properties is simple:
Create an instance of the JavaBean. The class name of the JavaBean appears in the
Elementthat represents the bean.
- For each property
Elementrepresenting a property of the bean):
- Determine the class of the property (by querying the
PropertyDescriptorfor the property)
- Create an instance of the property class (we'll call this the property value since it will be the value to which the property is set)
- Initialize the property-value instance using the contents of the property
- Set the property using the property-value instance and the property-write accessor (the setter)
- Determine the class of the property (by querying the
Likewise, the process for writing a JavaBean is straightforward:
For each property in the bean:
- Get the property value from the bean by calling the property's read accessor (the getter for the property)
- Convert the property value to a string
- Add an element that represents the property and contains the string representation of it to the DOM tree you're creating
- When all properties are encoded, write out the DOM tree as XML
You'll notice that, in the outline above, reference is made to property value without indicating whether the value is a bean, a primitive value, or an instance of some nonbean class. In the source code (which we'll discuss below), you'll see that these cases are handled separately and, in some cases, recursively. But the basic program structure reflects the concept that a property value is simply a property value, and the differences between the various kinds of properties (value, object, or bean) are just programming details.
The second major improvement -- customization -- is next.
Since XMLBeans is a utility package, meant to be used by a programmer, many of the features in the package are programming facilities, such as hook functions that allow a programmer to customize the package's behavior without doing violence to its operation.
One example of such a hook is the
getAsDOM() method. In Part 2 of this series, I introduced a method in the
XMLBeanWriter class to control how specific property types and JavaBeans are encoded as a DOM tree. The programmer has the option of specifying a method in a bean class called
XMLBeanWriter encounters a JavaBean with the
getAsDOM() method, it defers to the bean itself to self-encode as a DOM tree. If a JavaBean class doesn't define a
getAsDOM() method, the JavaBean is converted to XML in the standard way: by encoding all of its properties in XML.
DocumentFragment getAsDOM(Document d) by definition returns a DOM tree that represents its owner object. The
XMLBeanWriter class recognizes this signature both for beans and for property-type classes.
As noted above, if a JavaBean contains a method called
getAsDOM() (with the appropriate signature and return type),
XMLBeanWriter will call that method and return whatever that method returned as the DOM representation of the object. If
getAsDOM() doesn't exist,
XMLBeanWriter loops through all of the bean's properties, encoding each property as a DOM tree. Each DOM tree that represents a property is added to the list of properties in the DOM tree representing the bean, until all properties are encoded. In addition to using
getAsDOM to encode an entire bean,
XMLBeanWriter always looks to see if each property type defines
getAsDOM() and, if it does, uses that method to encode the property as a DOM tree.
So, for example, imagine we had a bean class called
CustomerAccount, representing the information for a particular customer in an online sales system. The designer of the
CustomerAccount class could choose to specify a method called
getAsDOM(). When applied to a
CustomerAccount object, the
XMLBeanWriter will detect and call the
getAsDOM() will then return the custom DOM representation of the
CustomerAccount JavaBean, instead of looking at all of the
Furthermore, imagine that the
CustomerAccount has a property called
Address, of type
CustomerAddress. If the
CustomerAccount doesn't have a method
getAsDOM(), but the
XMLBeanWriter will encode the
Address property by calling
getAsDOM() in either of these two places gives the bean designer a great deal of control over how the bean should be represented when the bean is converted to XML. In fact, at this point the designer has too much control because the existing version of
XMLBeanReader doesn't know how to handle generic XML. As of the end of Part 2 of this series, it was possible to write JavaBeans with
XMLBeanWriter that couldn't be read by
Of course, the new version of the XMLBeans package removes this limitation, by allowing specification of a
setAsDOM() method to match any
getAsDOM() method. Just as
XMLBeanWriter may use
getAsDOM() to retrieve the DOM representation of a bean (for writing to an XML file),
setAsDOM() to initialize a property value in a bean-object instance from the structure of a DOM subtree.
To provide more flexibility, an XMLBeans programmer may define
getDOMGetterName(), which should return the name of a method to use in place of the names
setAsDOM(). This allows more flexibility in naming, and also allows the object to decide at runtime which method to use for XML translation. This could be useful in a case in which there may be several alternate XML representations of an object, requiring several forms of
setAsDOM(), and the application needs to be able to choose the translation method pair at runtime.
You may remember from Part 2 that it wasn't easy to prevent
XMLBeanWriter from encoding properties. The only way to do so was to write a
getAsDOM() method that encoded all properties as a DOM tree, excluding the properties not desired in the output. Though this technique produces the desired result, it requires a lot of unnecessary programming. The third major improvement to the XMLBeans package solves this problem (and others) by integrating XMLBeans with the
XML property descriptors and property editors
Typically, a JavaBean designer controls the visibility of bean properties to external agents by creating a
BeanInfo object for the bean. One of the methods of the
BeanInfo interface is
PropertyDescriptor getPropertyDescriptors(), which returns an array of
PropertyDescriptor objects, one for each property that the bean defines.
java.beans.Introspector, the class responsible for analyzing JavaBeans, identifies bean properties by first trying to find a
BeanInfo object. If it finds one, it calls the
getPropertyDescriptors() method to get the list of properties. Only if this attempt fails does the
Introspector itself analyze the class to determine properties.
For example, imagine a JavaBean
ThingForSale with three properties:
int age, and
int price. A programmer could hide the
price attribute by simply defining a
getPropertyDescriptors() method in the
BeanInfo class for the bean
ThingForSaleBeanInfo that returns
PropertyDescriptor objects only for
weight. (For the fine details of how to do this, see my "The trick to controlling bean customization," (JavaWorld November 1997).)
PropertyDescriptor indicates not only the property, but also the property accessor methods (setter and getter methods). As described above, when analyzing a bean, the
Introspector first checks to see if it can get a list of
PropertyDescriptors from the
BeanInfo. If that fails, the
Introspector analyzes the bean class, building a list of methods that match the signatures
<i>Type</i> get<i>Property</i>() and
<i>void</i> set<i>Property</i>(<i>Type</i> value). From this list of methods, the
Introspector then creates its own
If you think about it for a moment, it should be pretty clear to you that the
setAsDOM() in a JavaBean could actually be used as setter and getter methods for bean properties. Instead of expressing the property in terms of its actual property type, though, these two methods get and set the property in terms of a DOM tree. In fact, each bean property could have its own DOM setter and DOM getter -- accessors for that property that set the property value as an
Element, and get the property value as a
DocumentFragment, respectively. We can even define a naming convention for them, just as we can for normal property accessors: