Skip to main content

Swing and Non Blocking JAX-WS

Posted by rbair on June 27, 2006 at 4:12 PM PDT

Yesterday I blogged about JAX-WS and how to use it with Swing. I tried to sneak by with a simple example that happened to block the EDT during the web service invocation (bad Richard! bad). Since I was called out on it, I took the opportunity to create a new JavaBean for working with background tasks and update my example to use it.

The problem with yesterday's example was this method:

    private void generateButtonActionPerformed(java.awt.event.ActionEvent evt) {                                         
        randomNumbersWS.setCount((Integer)countSpinner.getValue());
        final List results = randomNumbersWS.generateRandomNumbers();
        resultsList.setModel(new AbstractListModel() {
            public Object getElementAt(int index) {
                return results.get(index);
            }
            public int getSize() {
                return results.size();
            }
        });       
    }                                       

In this method I contact the webservice (a potentially lengthy process) while in the Event Dispatch Thread. In other words, while trying to contact the web service the entire GUI was frozen. Solid. Today we'll fix the problem by using a background thread.

For this task I created a new non visual JavaBean called BackgroundWorker. For those familiar with .NET, you'll notice the name. BackgroundWorker is a helper class that handles a lot of the threading work for you. BackgroundWorker is intended to be used in your GUI builder environment. To use it, you:

  • simply drag and drop a BackgroundWorker from the development palette
  • go to the property sheet and select the doInBackground event
  • write the code for your background task. Call "process" on the
    BackgroundWorker to publish whatever data you want to in the GUI
  • go to the property sheet and select the "publish" event
  • display the data in the gui (the data is part of the BackgroundEvent)

Those who've written multithreaded Swing code in the past have probably used SwingWorker. SwingWorker is an excellent piece of code, and handles a lot of the really sticky details for multithreading. BackgroundWorker, in fact, delegates to SwingWorker to do the actual multi-threading work. Why write BackroundWorker then? Well, when using a GUI builder, using a non visual component like BackgroundWorker can really be nice. I've found it much easier to "view" code visually in a GUI builder than to read and mentally visualize code.

Here's the new code, using a JXStatusBar and the BackgroundWorker. Oh, and by the way, I also implement the "started" and "done" events to make sure my GUI is properly disabled and re-enabled during this long process.

    private void getNumbersWorkerPublish(org.jdesktop.swingx.event.BackgroundEvent evt) {                                         
        final List results = (List)evt.getData();
        resultsList.setModel(new AbstractListModel() {
            public Object getElementAt(int index) {
                return results.get(index);
            }
            public int getSize() {
                return results.size();
            }
        });       
    }                                       

    private void getNumbersWorkerDoInBackground(org.jdesktop.swingx.event.BackgroundEvent evt) {                                               
        //ok, go and get the numbers. Call process when done fetching the numbers
        randomNumbersWS.setCount((Integer)countSpinner.getValue());
        final List results = randomNumbersWS.generateRandomNumbers();
        getNumbersWorker.process(results);
    }                                              

    private void getNumbersWorkerFinished(org.jdesktop.swingx.event.BackgroundEvent evt) {                                         
        //enable the button, update the status bar
        generateButton.setEnabled(true);
        statusLabel.setText("Ready");
    }                                        

    private void getNumbersWorkerStarted(org.jdesktop.swingx.event.BackgroundEvent evt) {                                        
        //disable the button, update the status bar
        generateButton.setEnabled(false);
        statusLabel.setText("Loading numbers from remote server...");
    }                                       

    private void generateButtonActionPerformed(java.awt.event.ActionEvent evt) {                                              
        getNumbersWorker.execute();
    }                                             

And here's the code for BackgroundWorker (I've omitted the event classes because they're boring :-). If there is enough interest, I'll push this code into SwingX).

/*
* BackgroundWorker.java
*
* Created on June 27, 2006, 9:02 AM
*/

package org.jdesktop.swingx;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.SwingUtilities;
import org.jdesktop.swingx.event.BackgroundEvent;
import org.jdesktop.swingx.event.BackgroundListener;
import org.jdesktop.swingx.util.SwingWorker;

/**
*
* @author rbair
*/
public class BackgroundWorker extends JavaBean {
    private List listeners = new ArrayList();
    private SwingWorker delegate;
   
    /** Creates a new instance of BackgroundWorker */
    public BackgroundWorker() {
    }

    public void execute() {
        //if another task is already executing, fire and IllegalStateException
        if (delegate != null) {
            throw new IllegalStateException("A background task is already in progress");
        }
       
        //create a new delegate
        synchronized(this) {
            delegate = new DelegateWorker();
            fireStartedEvent();
            delegate.execute();
        }
    }
   
    public void process(Object... chunks) {
        for (Object data : chunks) {
            firePublishEvent(data);
        }
    }
   
    public void addBackgroundListener(BackgroundListener listener) {
        if (!listeners.contains(listener)) {
            listeners.add(listener);
        }
    }
   
    public void removeBackgroundListener(BackgroundListener listener) {
        listeners.remove(listener);
    }
   
    public BackgroundListener[] getBackgroundListeners() {
        return listeners.toArray(new BackgroundListener[0]);
    }
   
    private void cleanup() {
        delegate = null;
    }
   
    private void fireStartedEvent() {
        BackgroundEvent evt = new BackgroundEvent(this);
        for (BackgroundListener listener : listeners) {
            listener.started(evt);
        }
    }
   
    private void fireDoneEvent() {
        BackgroundEvent evt = new BackgroundEvent(this);
        for (BackgroundListener listener : listeners) {
            listener.finished(evt);
        }
    }
   
    private void fireDoInBackgroundEvent() {
        BackgroundEvent evt = new BackgroundEvent(this);
        for (BackgroundListener listener : listeners) {
            listener.doInBackground(evt);
        }
    }
   
    private void firePublishEvent(Object data) {
        BackgroundEvent evt = new BackgroundEvent(BackgroundWorker.this, data);
        for (BackgroundListener listener : listeners) {
            listener.publish(evt);
        }
    }
   
    private final class DelegateWorker extends SwingWorker {
        protected Object doInBackground() throws Exception {
            fireDoInBackgroundEvent();
            return null;
        }

        protected void done() {
            cleanup();
            fireDoneEvent();
        }
    }
}