|
|
Contents: |
|
|
|
Related content: |
|
|
|
Subscriptions: |
|
|
| A look at XML data binding for Java using the open source
Castor project
Dennis
M. Sosnoski (mailto:dms@sosnoski.com?cc=&subject=Data
binding with Castor) President, Sosnoski Software Solutions,
Inc. 1 April 2002
XML data binding for Java is a powerful
alternative to XML document models for applications concerned mainly
with the data content of documents. In this article, enterprise Java
expert Dennis Sosnoski introduces data binding and discusses what makes
it so appealing. He then shows readers how to handle increasingly
complex documents using the open source Castor framework for Java data
binding. If your application cares more about XML as data than as
documents, you'll want to find out about this easy and efficient way of
handling XML and Java technologies.
Most approaches to working with XML documents in applications put the
emphasis on XML: You work with documents from an XML point of view and
program in terms of XML elements, attributes, and character data content.
This approach is great if your application is mainly concerned with the
XML structure of documents. For many applications that care more about the
data contained in documents than the documents themselves, data
binding offers a much simpler approach to working with XML.
Document models versus data
binding The document models discussed in previous articles
of this series (see Resources)
are the closest alternatives to data binding. Both document models and
data binding build document representations in memory, with two-way
conversions between the internal representation and standard text XML. The
difference between the two is that document models preserve the XML
structure as closely as possible, while data binding is concerned only
with the document data as used by your application.
To illustrate this point, Figure 1 shows the document model view of a
simple XML document. The document components -- in this case just elements
and text nodes -- are linked in a structure that mirrors the original XML
document. It's easy to relate the resulting tree of nodes to the original
document, but less easy to interpret the actual data present in the
tree.
Figure 1. Document model view of
document
If your application program uses a document model approach for XML,
you'll work with this type of tree. In this case, you'll use parent-child
relationships between nodes to navigate up and down the tree, and sibling
relationships between children of a common parent to navigate across the
tree. You'll be able to manipulate the tree structure at a very detailed
level, and when you serialize the tree as text, the generated XML document
will reflect your changes (such as including comments).
Now contrast Figure 1 with Figure 2, which shows a data binding view of
the same document. Here the structure of the original XML document is
almost completely hidden by the conversion, but the actual data is much
more easily seen and accessed through a pair of objects.
Figure 2. Data binding view of
document
Working with this data structure is just normal Java programming -- you
don't even need to know anything about XML! (Whoops, let's not go
too far here -- we expert consultants still need to make a
living...) Somebody on your project at least needs to understand how the
mapping between the data structure and the XML document is set up, but
this is still a big step in the direction of simplicity.
Data binding can provide other benefits beyond justprogramming
simplicity. Since it abstracts many of the document details, data binding
usually needs less memory than a document model approach. Consider, for
instance, the two data structures shown in the earlier figures: The
document model approach uses 10 separate objects, as compared to two for
data binding. With a lot less to build, it may also be faster to construct
the data binding representation for a document. Finally, access to the
data within your program can be much faster with the data binding approach
than with a document model, since you control how the data is represented
and stored. I'll get back to these points later.
If data binding is such great stuff, when would you want to use a
document model instead? The two cases that require a document model are:
- Your application is really concerned with the details of the
document structure. If you're writing an XML document editor, for
instance, you'll want to stick to a document model rather than using
data binding.
- The documents you're processing don't follow fixed structures. For
example, data binding wouldn't be a good approach for implementing a
general XML document database.
Many applications use XML for data transfer, but otherwise don't care
about the details of the document representation. These applications are
ideal candidates for data binding. If your application fits this pattern,
read on.
The Castor
framework Currently, a number of different frameworks
support XML data binding for Java, but there's no standard interface. This
will eventually change: JSR-031 in the Java Community Process (JCP) is
working on defining a standard (see Resources).
For now, pick one framework and learn to use its interface.
For this article, I've chosen to use the Castor data binding framework.
The Castor project uses a BSD-style license, making it available for use
in all types of applications (including completely proprietary ones).
Castor actually goes well beyond just XML data binding by supporting SQL
and LDAP bindings, though I'll ignore these other features for this
article. It has been under development since early 2000 and is currently
in an advanced beta state (generally usable, but you may need to update to
the current CVS version if you need a bug fix). See the Resources
section for links to the Castor site to get more details or to download
the software.
Default bindings Getting
started with Castor's XML data binding is very simple. You don't even need
to define an XML document format. As long as your data is represented in
JavaBean-like objects, Castor generates a document format to represent the
data automatically and later reconstruct the original data from that
document.
Data
binding dictionary Here's a mini-dictionary for some terms I
use in this article:
Marshalling is the process of generating an XML
representation for an object in memory. Just like with Java
serialization, the representation needs to include all dependent
objects: objects referenced by our main object, objects referenced
by those objects, and so on.
Unmarshalling is the reverse process, building an object
(and dependent objects) in memory from an XML representation.
Mapping is the set of rules used for marshalling and
unmarshalling. Castor has built-in rules defining the default
mapping described in this section of the article. It also allows you
to use a separate mapping file, as you'll see in the sections
below. |
So what does "JavaBean-like" mean? Real JavaBeans are visual components
that can be configured within development environments for use in GUI
layouts. Some practices that began with real JavaBeans have since become
widespread in the Java community, especially for data classes. I call a
class "JavaBean-like" if it follows these practices:
- The class is public
- It defines a public default (no argument) constructor
- It defines public
getX and setX methods
for access to property (data) values
Now that the technical definition is out of the way, I'll skip all this
when referring to one of these JavaBean-like classes and just call it a
"bean" class.
I'll use an airline flight timetable for the example code throughout
this article and start with a simple bean class representing a particular
flight to illustrate how this works.This bean includes four items of
information:
- The carrier (airline company) identifier
- The flight number
- The departure time
- The arrival time
Listing 1 below shows the code for handling the flight
information. Listing 1. Flight information
bean
public class FlightBean
{
private String m_carrier;
private int m_number;
private String m_departure;
private String m_arrival;
public FlightBean() {}
public void setCarrier(String carrier) {
m_carrier = carrier;
}
public String getCarrier() {
return m_carrier;
}
public void setNumber(int number) {
m_number = number;
}
public int getNumber() {
return m_number;
}
public void setDepartureTime(String time) {
m_departure = time;
}
public String getDepartureTime() {
return m_departure;
}
public void setArrivalTime(String time) {
m_arrival = time;
}
public String getArrivalTime() {
return m_arrival;
}
}
|
As you can see, on its own this bean's pretty boring, so I want to add
a class and use it in a default XML binding, as shown in Listing 2. Listing 2. Default data binding test
import java.io.*;
import org.exolab.castor.xml.*;
public class Test
{
public static void main(String[] argv) {
// build a test bean
FlightBean bean = new FlightBean();
bean.setCarrier("AR");
bean.setNumber(426);
bean.setDepartureTime("6:23a");
bean.setArrivalTime("8:42a");
try {
// write it out as XML
File file = new File("test.xml");
Writer writer = new FileWriter(file);
Marshaller.marshal(bean, writer);
// now restore the value and list what we get
Reader reader = new FileReader(file);
FlightBean read = (FlightBean)
Unmarshaller.unmarshal(FlightBean.class, reader);
System.out.println("Flight " + read.getCarrier() +
read.getNumber() + " departing at " +
read.getDepartureTime() +
" and arriving at " + read.getArrivalTime());
} catch (IOException ex) {
ex.printStackTrace(System.err);
} catch (MarshalException ex) {
ex.printStackTrace(System.err);
} catch (ValidationException ex) {
ex.printStackTrace(System.err);
}
}
}
|
Beyond beans with Castor Castor actually works with
more than just the JavaBean-like classes discussed in this article.
It can also access information from simple data object classes with
public member variables. For instance, with some minor changes to
the Test class, you could use the following definition
for the flight data and still end up with the same XML format:
public class FlightData
{
public String carrier;
public int number;
public String departure;
public String arrival;
}
|
A class has to be all one way or the other for Castor to work
with it properly. If the class defines any getX
or setX methods, Castor treats it as a bean and uses
only those methods for marshalling and
unmarshalling. |
In this code, you first construct a FlightBean bean and
initialize it with some canned values. You then write the bean to an
output file using Castor's default XML mapping for the bean. Finally, you
read the generated XML back in to reconstruct the bean, using the same
default mapping, and then print out the information from the reconstructed
bean. Here's the result:
Flight AR426 departing at 6:23a and arriving at 8:42a
This output shows that you've successfully round-tripped the flight
information (not bad for just two method calls). Now, I'll dig a little
deeper than just the console output.
Behind the
scenes
To see more of what's going on in this
example, take a look at the XML generated by the
Marshaller.marshal() call. Here's the document:
<?xml version="1.0"?>
<flight-bean number="426">
<arrival-time>8:42a</arrival-time>
<departure-time>6:23a</departure-time>
<carrier>AR</carrier>
</flight-bean>
|
Castor examines the object that you pass in the
Marshaller.marshal() call using Java introspection. In this
case, it finds the four property values you've defined. Castor creates an
element in the output XML (the root element of the document) to represent
the object as a whole. The element name is derived from the object class
name, flight-bean in this case. Castor then includes the
property values for this object in one of two ways. It creates::
- An attribute of the element for each primitive-valued property (in
this case only the
int -valued number property
exposed by the getNumber() method)
- A child element of the root element for each object-valued property
(all the others here, since they're strings).
The result is the XML document shown immediately above.
Changing the XML format If
you don't like Castor's default mapping format, you can easily change the
mapping. In the case of our flight information example, for instance,
let's suppose we want a more compact representation of the data. Using
attributes in place of child elements will help this, and we may even want
to use shorter names than the defaults. A document similar to the one
shown below would suit our needs nicely:
<?xml version="1.0"?>
<flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/>
|
Defining the mapping To
get Castor to use this format rather than the default, you first need to
define a mapping describing this format. The mapping description itself is
(big surprise) an XML document. Listing 3 shows the mapping to marshal the
bean to the format shown before. Listing 3. Mapping
for compact format
<!DOCTYPE databases PUBLIC
"-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.exolab.org/mapping.dtd">
<mapping>
<description>Basic mapping example</description>
<class name="FlightBean" auto-complete="true">
<map-to xml="flight"/>
<field name="carrier">
<bind-xml name="carrier" node="attribute"/>
</field>
<field name="departureTime">
<bind-xml name="depart" node="attribute"/>
</field>
<field name="arrivalTime">
<bind-xml name="arrive" node="attribute"/>
</field>
</class>
</mapping>
|
The class element defines mappings for a named class, in
this case FlightBean . You can tell Castor to use default
mappings for any properties of the class not specifically listed within
the body of the element by including the optional
auto-complete attribute with a value of true on
this element. This is convenient here, since the number
property is already being handled in the wanted way.
The child map-to element tells Castor to map instances of
the FlightBean class to flight elements in the
XML document. This element is optional if you continue with the default
element name of flight-bean seen in the example of default
mapping output in Behind
the scenes.
Finally, you can include a child field element for each
property that you want to handle differently from the default. These all
follow the same pattern: The name attribute gives the
property name, and the child bind-xml element tells Castor
how to map that property. In this case, you tell it to map each of the
properties to an attribute with the given name.
Using the mapping
Now that you have a defined mapping, you need to tell the Castor
framework to use that mapping when marshalling and unmarshalling the data.
Listing 4 shows the changes you'll need to make to the earlier code to
accomplish this. Listing 4. Marshall and unmarshall
with mapping
...
// write it out as XML (if not already present)
Mapping map = new Mapping();
map.loadMapping("mapping.xml");
File file = new File("test.xml");
Writer writer = new FileWriter(file);
Marshaller marshaller = new Marshaller(writer);
marshaller.setMapping(map);
marshaller.marshal(bean);
// now restore the value and list what we get
Reader reader = new FileReader(file);
Unmarshaller unmarshaller = new Unmarshaller(map);
FlightBean read = (FlightBean)unmarshaller.unmarshal(reader);
...
} catch (MappingException ex) {
ex.printStackTrace(System.err);
...
|
This code is a little more complicated than the earlier code using the
default mapping shown in Listing
2. Before any other operations, create a Mapping object
and load your mapping definition. The actual marshalling and unmarshalling
are also different. To use the mapping, you need to create
Marshaller and Unmarshaller objects, configure
them with the mapping, and call methods on these objects rather than the
static methods from the first example. Finally, you have to provide
handling for another exception type generated by mapping errors.
With these changes completed, you can try the test program again. The
console output is the same as the first example (shown in Listing
2), but now the XML document looks just like you wanted:
<?xml version="1.0"?>
<flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/>
|
Handling collections Now
that the individual flight data is in a form you like, you can define a
higher level of structure: route data. This structure will include to and
from airport identifiers, along with a collection of flights on that
route. Listing 5 shows an example of a bean class that holds this
information. Listing 5. Route information
bean
import java.util.ArrayList;
public class RouteBean
{
private String m_from;
private String m_to;
private ArrayList m_flights;
public RouteBean() {
m_flights = new ArrayList();
}
public void setFrom(String from) {
m_from = from;
}
public String getFrom() {
return m_from;
}
public void setTo(String to) {
m_to = to;
}
public String getTo() {
return m_to;
}
public ArrayList getFlights() {
return m_flights;
}
public void addFlight(FlightBean flight) {
m_flights.add(flight);
}
}
|
In this code, I've defined an addFlight() method to use
for setting the flights on the route one at a time. This is a convenient
approach for building the data structures in the test program, but
contrary to what you might expect, Castor doesn't use this method for
adding flights to the route when unmarshalling. Instead, it uses the
getFlights() method to access the collection of flights, then
adds directly to the collection.
Handling the collection of flights in the mapping just takes a
variation of the field element used in the last example
(shown in Listing
3). Listing 6 shows the modified mapping file. Listing 6. Mapping for route with flight
collection
<!DOCTYPE databases PUBLIC
"-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.exolab.org/mapping.dtd">
<mapping>
<description>Collection mapping example</description>
<class name="RouteBean">
<map-to xml="route"/>
<field name="from">
<bind-xml name="from" node="attribute"/>
</field>
<field name="to">
<bind-xml name="to" node="attribute"/>
</field>
<field name="flights" collection="collection" type="FlightBean">
<bind-xml name="flight"/>
</field>
</class>
<class name="FlightBean" auto-complete="true">
<field name="carrier">
<bind-xml name="carrier" node="attribute"/>
</field>
<field name="departureTime">
<bind-xml name="depart" node="attribute"/>
</field>
<field name="arrivalTime">
<bind-xml name="arrive" node="attribute"/>
</field>
</class>
</mapping>
|
Everything's pretty much the same as the last mapping (shown in Listing
3), except for the field element defining the
flights property of a RouteBean . This mapping
uses a pair of attributes not needed before. The collection
attribute with the value collection defines this property as
a java.util.Collection (other values define arrays, java.util.Vectors, and
so on). The type property defines the type of objects
included in the collection, with a fully qualified classname as the value.
Here the value is just FlightBean , since I didn't use a
package for the classes.
The other difference is that I no longer need to use a
map-to child element within the FlightBean class
element to define the element name for the binding. The field
element defining the flights property of the
RouteBean does that through its child bind-xml
element. Since the only way to marshal or unmarshal
FlightBean objects is through this property, they'll always
use the name set by this bind-xml element.
I won't bother to show the test program for this example, since the
data binding part is the same as the last example. Here's what the
generated XML document looks like for some sample data:
<?xml version="1.0"?>
<route from="SEA" to="LAX">
<flight carrier="AR" depart="6:23a" arrive="8:42a"
number="426"/>
<flight carrier="CA" depart="8:10a" arrive="10:52a"
number="833"/>
<flight carrier="AR" depart="9:00a" arrive="11:36a"
number="433"/>
</route>
|
Object references Now
you're finally ready to tackle the full flight timetable. For this you'll
add three more beans to the set:
AirportBean for airport information
CarrierBean for airline information
TimeTableBean to fit everything together
To keep it interesting, you'll also add some linkages between beans,
besides the ownership relationship between RouteBean and
FlightBean used in the last example (shown in Handling
collections).
Linking the beans For the
first added relationship, change FlightBean to reference the
carrier information directly instead of just using a code to identify the
carrier. Here are the changes to FlightBean :
public class FlightBean
{
private CarrierBean m_carrier;
...
public void setCarrier(CarrierBean carrier) {
m_carrier = carrier;
}
public CarrierBean getCarrier() {
return m_carrier;
}
...
}
|
Now, do the same thing for RouteBean referencing the
airport information:
public class RouteBean
{
private AirportBean m_from;
private AirportBean m_to;
...
public void setFrom(AirportBean from) {
m_from = from;
}
public AirportBean getFrom() {
return m_from;
}
public void setTo(AirportBean to) {
m_to = to;
}
public AirportBean getTo() {
return m_to;
}
...
}
|
I won't include the code for the added beans themselves, since they
don't show anything beyond what was done previously. You can download the
full code for all the examples in the code.jar download file (see Resources).
Mapping references You'll
need to use some other features of the mapping document to support
references between the objects that you are marshalling and unmarshalling.
Listing 7 shows the complete mapping: Listing 7.
Mapping for full timetable
<!DOCTYPE databases PUBLIC
"-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.exolab.org/mapping.dtd">
<mapping>
<description>Reference mapping example</description>
<class name="TimeTableBean">
<map-to xml="timetable"/>
<field name="carriers" type="CarrierBean" collection="collection">
<bind-xml name="carrier"/>
</field>
<field name="airports" type="AirportBean" collection="collection">
<bind-xml name="airport"/>
</field>
<field name="routes" type="RouteBean" collection="collection">
<bind-xml name="route"/>
</field>
</class>
<class name="CarrierBean" identity="ident" auto-complete="true">
<field name="ident">
<bind-xml name="ident" node="attribute"/>
</field>
</class>
<class name="AirportBean" identity="ident" auto-complete="true">
<field name="ident">
<bind-xml name="ident" node="attribute"/>
</field>
</class>
<class name="RouteBean">
<field name="from" type="AirportBean">
<bind-xml name="from" node="attribute" reference="true"/>
</field>
<field name="to" type="AirportBean">
<bind-xml name="to" node="attribute" reference="true"/>
</field>
<field name="flights" type="FlightBean" collection="collection">
<bind-xml name="flight"/>
</field>
</class>
<class name="FlightBean" auto-complete="true">
<field name="carrier">
<bind-xml name="carrier" node="attribute" reference="true"/>
</field>
<field name="departureTime">
<bind-xml name="depart" node="attribute"/>
</field>
<field name="arrivalTime">
<bind-xml name="arrive" node="attribute"/>
</field>
</class>
</mapping>
|
Aside from the added beans, the important change here is the addition
of identity and reference attributes. The
identity attribute of a class element tells
Castor that the named property is a unique identifier for an instance of
that class. Here I have both CarrierBean and
AirportBean define their ident properties as
identifiers.
The reference attribute of a bind-xml element
provides the other part of the linkage information Castor needs for the
mapping. A mapping with reference set to true
tells Castor to marshal the identifier for a referenced object, rather
than a copy of the object itself. I've used this for the references from
RouteBean to the linked AirportBean s for the two
ends of the route, and from FlightBean to the linked
CarrierBean .
When Castor unmarshals data using a mapping of this type, it
automatically converts object identifiers into references to the actual
objects. You need to make sure that the identifier values are truly
unique, even among objects of different types. With the data in this
example, that's not a problem: Carrier identifiers are two characters, and
airport identifiers are three characters, so they can never be the same.
You can easily avoid problems with this in cases where you do have
the potential for conflicts by prefixing each identifier with a unique
code for the type of object it represents.
The marshalled
timetable This example includes nothing new in the test
code, except more setup of the sample data. Listing 8 shows the XML
document generated by the marshalling: Listing 8. The
marshalled timetable
<?xml version="1.0"?>
<timetable>
<carrier ident="AR" rating="9">
<URL>http://www.arcticairlines.com</URL>
<name>Arctic Airlines</name>
</carrier>
<carrier ident="CA" rating="7">
<URL>http://www.combinedlines.com</URL>
<name>Combined Airlines</name>
</carrier>
<airport ident="SEA">
<location>Seattle, WA</location>
<name>Seattle-Tacoma International Airport</name>
</airport>
<airport ident="LAX">
<location>Los Angeles, CA</location>
<name>Los Angeles International Airport</name>
</airport>
<route from="SEA" to="LAX">
<flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/>
<flight carrier="CA" depart="8:10a" arrive="10:52a" number="833"/>
<flight carrier="AR" depart="9:00a" arrive="11:36a" number="433"/>
</route>
<route from="LAX" to="SEA">
<flight carrier="CA" depart="7:45a" arrive="10:20a" number="311"/>
<flight carrier="AR" depart="9:27a" arrive="12:04p" number="593"/>
<flight carrier="AR" depart="12:30p" arrive="3:07p" number="102"/>
</route>
</timetable>
|
Working with the data Now
that all the data for the timetable is finally set up, take a quick look
at how you can manipulate it in a program. Using data binding, you've
constructed a data structure for the timetable made up of several types of
beans. The application code that works with the data can use these beans
directly.
Suppose, for example, you want to find round-trip flight choices
between Seattle and Los Angeles, but only for carriers with a specified
minimum quality rating. Listing 9 shows the basic code to get this
information using the data-binding bean structure (see the source download
in Resources
for full details). Listing 9. Flight finder
code
private static void listFlights(TimeTableBean top, String from,
String to, int rating) {
// find the routes for outbound and inbound flights
Iterator r_iter = top.getRoutes().iterator();
RouteBean in = null;
RouteBean out = null;
while (r_iter.hasNext()) {
RouteBean route = (RouteBean)r_iter.next();
if (route.getFrom().getIdent().equals(from) &&
route.getTo().getIdent().equals(to)) {
out = route;
} else if (route.getFrom().getIdent().equals(to) &&
route.getTo().getIdent().equals(from)) {
in = route;
}
}
// make sure we found the routes
if (in != null && out != null) {
// find outbound flights meeting carrier rating requirement
Iterator o_iter = out.getFlights().iterator();
while (o_iter.hasNext()) {
FlightBean o_flight = (FlightBean)o_iter.next();
if (o_flight.getCarrier().getRating() >= rating) {
// find inbound flights meeting carrier rating
// requirement, and leaving after outbound arrives
int time = timeToMinute(o_flight.getArrivalTime());
Iterator i_iter = in.getFlights().iterator();
while (i_iter.hasNext()) {
FlightBean i_flight = (FlightBean)i_iter.next();
if (i_flight.getCarrier().getRating() >= rating
&&
timeToMinute(i_flight.getDepartureTime())
> time) {
// list the flight combination
printFlights(o_flight, i_flight, from, to);
}
}
}
}
}
}
|
You can try this out using the sample data shown earlier in Listing
8. If you ask for flights from Seattle (SEA) to Los Angeles (LAX) with
carriers ranked 8 or higher, the results are:
Leave SEA on Arctic Airlines 426 at 6:23a
return from LAX on Arctic Airlines 593 at 9:27a
Leave SEA on Arctic Airlines 426 at 6:23a
return from LAX on Arctic Airlines 102 at 12:30p
Leave SEA on Arctic Airlines 433 at 9:00a
return from LAX on Arctic Airlines 102 at 12:30p
|
Document model
comparison I won't try going through the equivalent code
using one of the XML document models here; that is complicated enough to
warrant a separate article. The simplest way of approaching the problem is
probably to parse through the carrier elements first and
build a map linking each identifier code to the corresponding element.
Then, use logic similar to the example code in Listing
9. Each step is more complicated than the bean example, because the
code works with XML components rather than the actual data values.
Performance might also be much worse -- not an issue if you're just doing
a few operations with the data, but a major concern if it's at the core of
your application.
The difference (in terms of both code complexity and performance) is
even greater if you use more data type conversions in the mapping between
beans and XML. For instance, if you work with the flight times a lot,
you'd probably want to convert the text times to a better internal
representation (such as minute number in the day as shown in Listing
9). You could either define alternate get and
set methods for text versus internal forms (setting the
mapping to use only the text form) or define a custom
org.exolab.castor.mapping.FieldHandler implementation for
Castor to use with these values. Keeping the time values in an internal
form allows you to skip the conversion when you try to match up flights in
Listing 9 and makes the processing even faster.
Castor provides a lot of hooks for customization, beyond what I've
discussed in this article: Special FieldHandler s are just one
example. Ideally, the sample code and discussion have given you a feeling
for the power and flexibility of the framework. I encourage you to
experiment further with Castor on your own. I think you'll find Castor as
useful (and as much fun) as I have.
Conclusions Data binding
is a great alternative to document models in applications that use XML for
data exchange. It simplifies your programming because you no longer need
to think in terms of XML. Instead, you can work directly with objects that
represent the meaning of the data as used by your application. It also
offers the potential for better memory and processor performance than
document models.
I've gone through a series of increasingly complex examples of data
binding using the Castor framework in this article. All these examples use
what I call direct data binding: The developer defines the classes
based on the data, then maps the data to an XML document structure. I'll
explore another approach in the next article: schema data binding,
which takes a document schema (such as DTD, XML Schema, or another flavor)
and generates code corresponding to that schema.
Castor supports the schema approach as well as the direct binding
you've seen in this article, so you'll see more of Castor in the follow
up. I'll also look at the progress on JSR-031 for a Java Data Binding
standard and compare the performance between approaches. Watch this space
for more on XML data binding in Java, coming soon to an IBM
developerWorks near you.
Resources
- Participate in the discussion forum on this
article. (You can also click Discuss at the top or bottom of the
article to access the forum.)
- Download the example programs and libraries used for this article as
a jar
archive file.
- Check out the latest Castor developments on the project home page.
- Review the author's previous developerWorks articles covering
performance
(September 2001) and usage
(February 2002) comparisons for Java XML document models.
- For other authors' views on using Castor XML data binding, read
Scott L. Bain's "XML Data
Binding with Castor" paper, and Dion Almaer's OnJava.com
article, "XML
Data Binding with Castor".
- Compare JAXB and Castor XML data binding in Sam Brodkin's
JavaWorld article "Use
XML data binding to do your laundry".
- See how Castor's data binding for data stores compares with Sun JDO
in Jacek Kruszelnicki's JavaWorldarticle "Persist
data with Java Data Objects, Part 2".
- Finally, take a look at IBM
WebSphere Studio Application Developer, an easy-to-use, integrated
development environment for building, testing, and deploying J2EE
applications, including generating XML documents from DTDs and schemas.
- Other links
About the
author Dennis Sosnoski (dms@sosnoski.com) is the founder
and lead consultant of Seattle-area Java consulting company Sosnoski Software Solutions,
Inc. His professional software development experience spans over
30 years, with the last several years focused on server-side Java
technologies including servlets, Enterprise JavaBeans, and XML. He's
a frequent speaker on XML in Java and J2EE technologies at
conferences nationwide, and chairs the Seattle Java-XML
SIG. |
|