In this interview with Artima,
Shannon Hickey, spec lead for the Beans Binding API, JSR 295, discusses the
challenges of Java data binding, and how the JSR 295 API simplifies that task.
Frank Sommers:
What problem does the Data Binding API solve?
Shannon Hickey: Data binding is about
making connections between beans or objects, and about syncing up properties of
two different objects.
Data binding is particularly interesting with Swing
components, where you have application objects, and you want to bind them to
user interface components. That way, the UI components can display the values
of those application objects. Further, you can use the UI components to allow
the user to set the values of the application objects.
Before the Beans Binding framework, a lot of boilerplate
code had to be written to listen for changes of one property of an object, set
a value of another object's property, and vice versa. The initial goal of Beans
Binding is to simplify making connections between such properties and objects.
There is an additional emphasis on Swing components, and especially on binding
to complex Swing components, such as a JList, JTable and JTree.
Frank Sommers: What
are the special technical challenges in the use-cases you mentioned? Couldn't
you just use the JavaBeans properties and listener patterns to notify one
object of changes in another object?
Shannon Hickey: You certainly can. For
the simple case of connecting two properties of JavaBeans, you can write code
to add a PropertyChangeListener to the first JavaBean, listen for changes, and then set the value on the
second bean, and vice versa. You can use those two listeners to propagate the
changes back and forth. That's simple with JavaBeans, although it's a lot
easier with Beans Binding, where you can create an object with a strategy that
dictates how the beans should sync up.
This gets more complex with some Swing components that don't
behave as proper beans. For example, the TextComponent
fires DocumentChangeEvents when the value of the text
changes. In that case, you need to add a DocumentListener
to the document of a TextComponent to listen for changes.
Likewise, JComboBox fires ActionEvents,
and JList fires ListSelectionEvents.
You need to remember all that boilerplate code when interacting with different
components to figure out how you can connect them.
Even more complex is the case with something like JList or JTable when you work
with the Collections classes. Let's say you have a List of Items. You would
have to create a custom model to set on your target component, and propagate
the changes from the List to that model. With Beans Binding, you can create a
binding between the List and a JTable or a JList, and have the List behind the scenes automatically
create the model and listen for changes to display in the table.
Frank Sommers:
How does Beans Binding reduce the boilerplate code developers have to write
when connecting bean properties?
Shannon Hickey: Beans Binding raises the abstraction level for connecting bean
properties.
The first thing we've done is create an abstract Property
class that represents a way to get at a property on some source object. Beans Binding provides two implementations of Property: One is BeanProperty, which uses a simple property path syntax to
indicate which property is being referenced. With this simple syntax, if you have
a Person object with a mother property, and you wanted to create a Property
that refers to the mother's firstName, you could
create a BeanProperty called mother.firstName.
The other way to specify properties is via more complex expressions of a
property, using the Java EL expression language. For that latter use-case, we
define an ELProperty class.
As a result of that higher level of abstraction, there is a
lot less boilerplate code for you to write. Suppose you want to bind the
selected item of a JList to the text of a TextField. With Beans Binding, you would create a Property
that describes the selected item, and another Property that represents the text
in a JTextField. Then you would just create the
binding between those two Property objects. There is no more figuring out what
type of listener needs to be added to the JList, or
what type of listener needs to be added to the TextComponent.
You're using a common pattern, regardless of that you're binding.
In addition to raising the level of abstraction for your
code, Beans Binding does more: It allows you to traverse paths along
properties, and reference multiple methods on those paths. For instance, you
can create a BeanProperty representing the first name
of a person's mother. That Property, in effect, represents the call to getMother().getFirstName(). And that Property also
listens to property changes along the path and updates its value any time any
of the objects in the path change. If the person's mother property
changes, or the mother's first name changes, the first name property value will
automatically be updated.
In addition, the Person itself may be referenced from
another bean. In that case, even changing the Person would update the property.
For example, you may have a map of some keys to Person objects. When the value of Person in the map changes, Beans Binding will
update the mother's first name property in response to that change.
Once you start dealing with more complex paths, there's a
lot more boiler plate code you would have to write without Beans Binding.
Consider this example: You want to bind a JTable to a
list of Person objects. In addition, you also want to bind some other object to
the table's selected item: For example, you want to show the selected person's
mother's first name in a TextField.
Any time the selected item in the table changes,
you want that first name field's text to update. But you also want to update
that first name field if the person's mother's first name is changed in the
list. You would have to write a lot of error-prone, repetitious code that Beans
Binding simplifies for you.
Frank Sommers:
You described how BeanProperty and ELProperty abstract away the complexity of adding listeners
to a bean. How do you wire up these properties for notifications with Beans
Binding?
Shannon Hickey: Suppose you want to
bind the Person object's firstName property to a text
field. You start by creating the Property object: invoke the BeanProperty.create() method with the string "firstName"
to create the first bean property. Similarly, create a bean property called
"text" that represents the text property of the TextComponent.
Then, you create an auto-binding with Bindings.createAutoBindings(), passing that method four parameters: the first bean (the
person), the "firstName" property, which
says that you want to apply that property to the Person object, then the TextField, and then the "text" property.
When creating an auto-binding, you also must pass in the
update strategy. An auto-binding has three update strategies: read once, read,
and read-write. The first strategy means that the property is read only when
the binding is made. With read, any time the first property changes,
the binding will refresh the target from the source. And read-write applies the
changes in both directions.
Frank Sommers:
What are the requirements for a bean to participate in this process?
Shannon Hickey: The BeanProperty and ELProperty,
which are our implementations, require JavaBeans: the properties these bindings
represent are JavaBeans properties.
For a property to be readable, the property requires a get() or is()set() method as well. If there are not going to
be any changes to the bean, then that's enough, such as when you just want to
display the value of something in a label without the source ever changing.
Once you start dealing with bindings whose source is going
to change, you need to fire property change notifications in order to have them
work properly with BeanProperty and ELProperty. This follows the common pattern of writing a
bean: a bean needs to maintain a list of property change listeners, and notify
those listeners with the correct property names and values. I believe the Swing
Application Framework [JSR 296] may even provide an abstract bean you can build
on that follows all these patterns.
Additionally, BeanProperty and ELProperty work with a Map, in that they can use the
property name as the key into a map. If you map the string "key" to
some value, fetching the bean property with that string represents calling
get() with that string on that map. In order to respond to changes in the map,
the map has to be an instance of ObservableMap. ObservableMap and ObservableList
are provided by this project. We have convenience methods that take a map and
wrap it into observable map.
There is also a third set of things that we nicely and
quietly deal with. As I mentioned, many of the Swing components are not proper
JavaBeans, or do not have proper JavaBean properties
with respect to some of the interesting things you'd want to listen to. For
example, JSlider doesn't provide a value property, a TextComponent doesn't offer a text property, and JTable doesn't have a selectedElement
property. Those are situations where you need to know all the boiler plate code
to write in order to listen for changes.
To address that issue, in BeansBinding
we provide the ability to register synthetic properties. A synthetic property
creates a drop-in replacement for a bean to be used in Beans Binding.
JTable.selectedElement is an
example of this. Since JTable doesn't have a selectedElement property, we provide a behind the scenes a
drop-in, which is a bean that provides a selectedElement
method, listens to JTable changes, and updates what
it considers to be the table's selectedElement in
response to those changes. That allows developers to just bind selectedElement like they would any other proper JavaBean property.
The way we support these synthetic properties is via a
registry of what we call adapters. We hope to eventually open this registry up
so that third parties can provide adapters for their beans. We currently
provide synthetic properties for the interesting Swing components. In addition
to JTable.selectedElement, we also provide List.selectedElement, ComboBox.selectedItem,
TextComponent.text, a Spinner and Slider's value: all
these things should be proper JavaBean properties,
but aren't. We could fix these in future versions of Swing,
but Beans Binding is designed to work with Java 5.0 and forward.
Frank Sommers:
Swing's single event threading rule states that you are only supposed to change
the state of a Swing component in the event handling thread. When you update
the property of a visual bean, such as a Swing component, does the Beans
Binding API take into account the Swing event handling thread?
Shannon Hickey: No, everything occurs
on the thread in which the event is called. You can use one of the existing
patterns and tools that are available to address that. If you're getting an
object from the network, and you want to change the binding, then you need to
change that binding on the event handler thread.
Frank Sommers:
The Beans Binding API has been in development for a while now. How has the API
changed over time? What sort of feedback from the expert group and users
benefited the API the most?
Shannon Hickey: JSR 295 was initiated
by Scott Violet before he left Sun, and I inherited it form him. The
implementation of the API changed quite significantly with respect to the
design goals that Scott had set out.
In the initial version, EL was a requirement. A binding was
created with four parameters: source and target object, and two strings. The
first string was interpreted as EL all the time, and the second one was
interpreted as a simple path. A lot of the expert group and the community
didn't want to have to deal with EL. With this community, we decided to create
this abstract Property class so people can use BeanProperty
or, if they wanted to, ELProperty. Users can even
create their implementations of Property. So the API is no longer hard-coded to
EL. That was a big win, and it got a lot people happy and interested in the
project.
The second major change was about ad-hoc parameters for
configuring special bindings. For example, when binding to a JTable, you want the table to display properties of some
elements in different columns. In the old implementation, doing this was
confusing, and there was no type safety. The second major re-architecture for
1.0 was to create binding subclasses for these complex cases. There is a JTableBinding, a JComboBox
binding, and a JList binding. In the JTableBinding there are methods for adding column binding,
and column bindings have methods on them for setting whether or not they're
editable and what class they represent.
Frank Sommers: There
are currently several proposals to add native property support to the Java
language. To what extent would language-level properties make data binding an
easier task?
Shannon Hickey: There has been a lot
of confusion about Beans Binding and its relation to proper property support in
the language. Some people are afraid that Beans Binding will somehow stall
adding property support for the language.
Any effort about adding property support to the language is
happening separately from Beans Binding. The Beans Binding API is designed to
work with Java 5.0, and with old JavaBeans, and to solve an existing problem.
Had actual property support been in the language, it would
have made some of the architecture in Beans Binding different than what it is
now. My goal with the Beans Binding project is to ensure that if, and when,
property support is added to the language, it's easy to create an adapter or
custom property type in Beans Binding. We designed the abstract Property class
such that when proper language support is added, we could provide an additional
implementation that uses native properties.
Frank Sommers:
There are several other projects in the Java community that aim to address the
problem of binding JavaBeans properties. How does the JSR 295 API relate to
those projects?
Shannon Hickey: As I mentioned, I
inherited the JSR 295 API from Scott Violet, and Scott had evaluated related
projects in the community at the time JSR 295 commenced. So I cannot speak what
specific projects and APIs influenced the initial design of the Beans Binding
API.
Currently, the Beans Binding API and the related java.net
project work closely with several other community initiatives, especially in
the Swing community. Perhaps the most significant example of a project using
the JSR 295 API is NetBeans 6, which already has
support for the Beans Binding project in its GUI builder.
In NetBeans 6's UI designer,
dragging a database table bean onto a form will automatically create bindings
between some database object and Swing components. NetBeans
also provides form editors for creating bindings. Since NetBeans
6 also supports the Swing Application Framework API [JSR 296], it is possible
to rapidly create a UI that relies on the Swing App Framework as well as uses
the Beans Binding framework.
The Beans Binding project also makes use of several other
projects from the Java community. The EL expression language we use in specifying
ELProperty objects is from the GlassFish
project, with some minor changes that we needed for Beans Binding. Hopefully,
we can get those changes back into the official EL version in the future.
Frank Sommers:
How stable is the Beans Binding API now? What are your future plans for JSR
295?
Shannon Hickey: The JSR is ongoing, and the end goal is to have it be a part of Java 7. The reference implementation is being developed in the open on java.net. I've been releasing builds when I think the project code is stable, and that has allowed people to provide feedback and to start using it even now. It is being used now quite widely, and the feedback from the community has really been invaluable.