Geertjan is a DZone Zone Leader and has posted 459 posts at DZone. You can read more from them at their website. View Full User Profile

Spring: How to Create Decoupled Swing Components

04.05.2008
| 44812 views |
  • submit to reddit

The Spring Framework's applicability in the context of Swing seems to be underhighlighted, at least when one looks around on the web. What does Spring have to offer in this context? Rather than a highly theoretical discussion, let's look at a complete, compilable example, step by step, and draw our conclusions from there.

Let's first look at the Swing application's source structure that we will have at the end of this article:

The first aspect that is immediately observable from the above source structure is that we have one class for each of the Swing components we will deal with, i.e., those are the files above that are not highlighted—a JFrame, a JPanel, a JTextField, and a JButton. We also have a class for the ActionListener that we will add to our JButton. Everything in these classes, the ones that are not highlighted, is pure Java and pure Swing, there's nothing wacky going on there, each of the classes extends or implements the classes you would expect—JFrame, JPanel, JTextField, JButton, and ActionListener. Finally, there are two Spring-specific files, both of which are highlighted above. There's a class that will launch the entire application, using Spring-specific classes and the Spring configuration file that, as we will see, will hook everything together. When the application is run, the result is as follows:

It is a very humble result, admittedly. And, in this part, the Swing components don't even do anything meaningful. The point here is to simply add the Swing components to the application without adding any Java code to make that possible. However, just that is more than enough to illustrate most of the basic principles of the applicability of Spring in the context of Swing. Normally, in the absence of Spring, the Swing components in our application would be added to each other in Java code. You would use the "add" method on the JFrame to add the JPanel to the JFrame and you would use the "add" method on the JPanel to add the JTextField and JButton to it. However, in this case, there is no code like that. In fact, none of the classes refer to any of the other classes. They are all self-contained. They are, in fact, decoupled from each other. Here's the JFrame:

package springexample;

import java.awt.Dimension;
import javax.swing.JFrame;

public class MyJFrame extends JFrame {

public void init() {

java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setSize(new Dimension(300, 300));
setVisible(true);
}
});

}
}

The other classes are similar, providing only the code that is relevant to the specific Swing component it defines. Even the ActionListener is decoupled. The ActionListener is not added to the JButton in the code in the Java classes above. Everything is isolated, separated, insulated, pared down, and self-contained.

All this is possible because of the fact that the application has the Spring JARs on its classpath, together with a launcher that locates the Spring configuration file, which in turn is defined exactly as follows:

After the first five lines, which provide the header, consisting of the relevant Spring namespace, let's look at what each of the main elements above does:

  • Line 6-9: Sets the JFrame, specifying its "title" property and its "contentPane" property. Note that we also set an "init-method" attribute, which provides a value that is the name of the method in the JFrame class that we want to have used to instantiate the class.
  • Line 10-19: Sets the main JPanel, consisting of two JTextFields and another instance of the same JPanel, defined in line 20-27. We also pass a property called "axis" and a property called "panelComponents". Both of these will be handled by our JPanel class. The first of these is used to set the "axis" property of the layout used by the JPanel, which is set to BoxLayout. The "panelComponents" property will be filled by our three Swing components. An iterator in the JPanel class will loop through the received components, adding each to the JFrame as it does so. All this will happen in the "init" method, which is set as the value of the "init-method" attribute.
  • Line 20-27: Sets the lower panel, which in this case only provides a JButton. Notice that this JPanel is defined by the same class as the one used to define the main panel. So, we are reusing the JPanel via the Spring configuration file.
  • Line 28: Sets the first JTextField.
  • Line 29: Sets the second JTextField.
  • Line 30-37: Sets the JButton, together with its "text" property and "actionListener" property. Notice that we didn't set the "text" property of the JTextFields, which we could have done, had we wanted to do so. You can either set the "text" property, as well as most other properties, right in the Spring configuration file, if you chose to do so. Your choice.
  • Line 38: Sets the ActionListener.
  • That's the complete file and it is mostly quite readable with the naked eye, no big surprises, apart from (if you're new to this) the flexibility and power offered by the Spring configuration file. Now let's look at what's in those classes, exactly. The JFrame is already shown above. Below follow all the other classes:

    package springexample;

    import java.awt.Component;
    import java.util.Iterator;
    import java.util.List;
    import javax.swing.BoxLayout;
    import javax.swing.JPanel;

    public class MyJPanel extends JPanel {

    private List panelComponents;
    private int axis;

    public void setAxis(int axis) {
    this.axis = axis;
    }

    public void setPanelComponents(List panelComponents) {
    this.panelComponents = panelComponents;
    }

    public void init() {
    setLayout(new BoxLayout(this, axis));
    for (Iterator iter = panelComponents.iterator(); iter.hasNext();) {
    Component component = (Component) iter.next();
    add(component);
    }
    }
    }
    package springexample;

    import javax.swing.JTextField;

    public class MyJTextField extends JTextField {

    public void init() {
    setText("hello world");
    }
    }
    package springexample;

    import java.awt.event.ActionListener;
    import javax.swing.JButton;

    public class MyJButton extends JButton {

    private ActionListener actionListener;

    public void setActionListener(ActionListener actionListener) {
    this.actionListener = actionListener;
    }

    public void init() {
    this.addActionListener(actionListener);
    }
    }
    package springexample;

    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.JOptionPane;

    public class MyActionListener implements ActionListener {

    public void actionPerformed(ActionEvent e) {
    JOptionPane.showMessageDialog(null, "Hello from Spring!");
    }
    }

    Though we've added, in the JButton, an ActionListener, we haven't specified which ActionListener. To change the ActionListener, you'd simply change line 38 in the Spring configuration file above. Just change the "classname" attribute to something different and then you're done. Clearly, there's no Java code that connects the "MyActionListener" class to the "MyJButton" class. Similarly, there's no Java code connecting the Swing components to each other. To the end user, there's no difference. To the developer, everything that connects anything is set in the Spring configuration file, thus creating decoupled Swing components, as well as decoupled behavior (i.e., as shown by the ActionListener).

    Finally, let's look at the launcher class, which uses Spring-specific code, but is self-explanatory:

    package springexample;

    import org.springframework.context.support.ClassPathXmlApplicationContext;

    public class SpringLauncher {
    public static void main(String[] args) {
    String[] contextPaths = new String[]{"springexample/app-context.xml"};
    new ClassPathXmlApplicationContext(contextPaths);
    }
    }

    Now, what do we gain from all of this? Does Spring really encourage a complete disintegration of Swing components down to this low level? Not necessarily, but possibly. The point is, you can decouple as far as you like and as much as you like. All within the same application. But why would you want to do so? Now that we understand the WHAT, let's look at the WHY. There's whole books on the WHY. The WHY is "dependency injection" and "loose coupling". One nice advantage is that, because your components are really pared down and separated from each other, it is easier to test them. Your JUnit tests (or other tests) will be easier to write because there are no encumbrances from one class to another. Also, what about the plumbing code, i.e., the wiring, that you would normally need to maintain? All of it is now within the Spring configuration file; none of it is in the Java code. Also note that the Spring configuration file is non-invasive. You can simply drop it into an existing application, write a bean for the main class, and then add the launcher code. Then you're good to go. So, integration with your existing Swing applications can occur without you needing to change those existing applications, unless you want to do so to make the fit even better.

    Finally, look again at that JPanel. That's a reusable Swing component (just like the other Swing components used here, such as the JTextField, which you've reused too). You've used it twice within the same application, for different purposes. In each case, you have a lot of control over the layout of the JPanel, right from within the Spring configuration file so that, even though you're using the same JPanel, its content could be different each time you reuse it. Also, notice that we're using a generic List object there. Instead of specifying a particular Swing component, which would have made the JPanel less reusable, we've opted for a generic List object so that we're not limiting the components it is able to handle. That's something to consider when creating decoupled Swing components and, therefore, working in a decoupled way has an impact on your design process.

    I hope this serves as a useful introduction to the Spring Framework in the context of Swing. In the next part, we will let the behavior of one of the Swing components (the JButton) cause something to happen to one of the others (one of the JTextFields). Many thanks to Chad Woolley, who wrote a great tutorial on Swing and Spring from which I learned a lot. That tutorial was the starting point for this one. Another document to look at is Java Programming/Spring Framework by Hyad on Wikipedia.

     

    AttachmentSize
    spring-1.png200.23 KB
    spring-2.png19.49 KB
    spring-3.png7.97 KB
    Published at DZone with permission of its author, Geertjan Wielenga.

    Comments

    Developer Dude replied on Mon, 2009/08/24 - 3:20pm

    Also, now having read the complete article, I disagree with some of the premises. I would not setup such a Swing component. For one thing I would not be setting user readable text in either the code or the Spring XML. I would have a resource handler to retrieve those strings from a resource data store. This is a standard pattern in well written Swing apps - especially those interested in localization/personalization.

    Now, the resource handler itself, could be configured using Spring. Also, which handler to use could be configured in the Swing app. That would be a good example.

    I also would not be setting up the layout or most listeners (especially if they were for internal components in the component I was configuring) in Spring. Much of that doesn't change outside of the component. External components, yes - take for example the case of a user double clicking on an object in a tree or table, and getting an object editor. Not only would you probably want to have which editor to use be in Spring, but you would quite possibly want the editor to be able to listen to certain changes in the tree.

    In short, a little less granularity and more higher level configuration.

    I am not saying that there aren't times when you don't want fine grained control/configuration, maybe for example a highly reusable property sheet. But many components will be mostly static at the level of the above example.

    As for testing - in Swing components, I find integration the area of most bugs, not the low level individual components. GUIs have quite different testing needs from other components, and don't always benefit from being tested by themselves - more often in conjunction with other components.

    Comment viewing options

    Select your preferred way to display the comments and click "Save settings" to activate your changes.