Now let's take a look at the Spin solution. Here's the code:
public void actionPerformed(ActionEvent e)
{
label.setText("...");
label.setText(bean.getValue());
}
public void propertyChange(PropertyChangeEvent ev)
{
label.setText((String)ev.getNewValue());
}
public String getValue()
{
String value;
// extensive calculation
return value;
}
public void setValue(String value)
{
this.value = value;
firePropertyChange(value);
}
Hey wait a minute! It's the same code as shown in the previous section Standard Swing. But the colors have changed - GUI.java
is completely green and BeanImpl.java
is completely red - how can this be?
Spin makes this solution possible - as you can see with no impact on the old code. An invisible layer between these two classes handles all threading issues transparently.
All we have to do is to spin-off the bean from the EDT. For this we wrap the bean in an instance of type Spin
. The result can safely be casted to any interface the bean (or one of its superclasses) implements. The best place to do this is before a reference to this bean is passed to a GUI component (why bother the programmer of the GUI with this little detail):
bean = (Bean)Spin.off(bean);
The only restriction here is that the Bean has to be broken into interface and implementation. The GUI components will only use the interface! The following picture shows how Spin connects the GUI and the bean. Calls on the EDT from the GUI to the bean are brokered to other threads invocating the beans functionality:
The need for an interface isn't really a restriction:
Spin
instance assuming that the bean allows for an PropertyChangeListener
to be added as a callback (this could be any interface like foo.DataChangedListener):
bean.addPropertyChangeListener((PropertyChangeListener)Spin.over(gui);
This is all you have to know to get Spin to work in your project. If you're interested in the internals of Spin go on to the next section.
Spin is built on top of virtual proxies and a technique borrowed from the java.awt.Dialog component. While a modal dialog has to wait for user input, the EDT is rerouted to the swing event processing to handle further events.
The following diagram shows how this is used in Spin. Each invocation of a bean's method is intercepted and handled by a SpinOffEvaluator:
getValue() is evaluated asynchronously on another thread (customizable with a Starter) while Swing events are dispatched through a Dispatcher. Once the call to the bean returns the dispatching of events is stopped and the EDT is free to return to the standard event processing:
For asynchronous notifications from the bean to the GUI we reuse the technique introduced in the previous sections. But this time the call to invokeAndWait()
is encapsulated by Spin with a SpinOverEvaluator:
Please take a look at the full API for details on how to customize Spin.
Although Spin handles threading transparently there are caveats with spin-off that you should be aware of:
Cancel
functionality and the retrieval of the current state of invocation or intermediate results (e.g. for updating a progress bar or incremental filling of a table).Spin is a small library that concentrates on offering a powerful solution to build non-freezing Swing applications. Spin enforces good application design by separating the GUI and non-visual components through interfaces. If it is used wisely in an application framework, the GUI programmers will never have to think about threads again.
Spin comes with several demonstration classes that show how to solve formerly challenging Swing programming problems with ease: