Spring: How to Create Decoupled Swing Components
The Spring Framework's applicability in the context of Swing seems to be underhighlighted, at least when one looks around on the web.
Join the DZone community and get the full member experience.
Join For FreeThe 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:
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.
Opinions expressed by DZone contributors are their own.
Comments