Handcoding with Beans

So far, we’ve seen how to create and use beans within a bean application builder environment. That is the primary motivation for JavaBeans, at least in GUI development. But beans are not limited to being used by automated tools. There’s no reason we can’t use beans in handwritten code. You could use a builder to assemble beans for the user interface of your application and then load that serialized bean or a collection of beans in your own code, just as NetBeans does when told to use object serialization. We’ll give an example of that in a moment.

Bean Instantiation and Type Management

Beans are an abstraction over simple Java classes. They add, by convention, features that are not part of the Java language. To enable certain additional capabilities of JavaBeans, we use special tools that take the place of basic language operations. Specifically, when working with beans, we are provided with replacements for three basic Java operations: creating an object with new, checking the type of an object with the instanceof operator, and casting a type with a cast expression. In place of these, use the corresponding static methods of the java.beans.Beans class, shown in Table 22-1.

Table 22-1. Methods of the java.beans.Beans class

Operator

Equivalent

New

Beans.instantiate(classloader, name)

Instanceof

Beans.isInstanceOf(object, class)

Beans.instantiate() is the new operation for beans. It takes a class loader and the name of a bean class or serialized bean as arguments. Its advantage over the plain new operator is that it can also load beans from a serialized form. If you use instantiate(), you don’t have to specify in advance whether you will provide the bean as a class or as a serialized object. The instantiate() method first tries to load a resource file based on the name bean, by turning package-style names (with dots) into a path-style name with slashes and then appending the suffix .ser. For example, magicbeans.NumericField becomes magicbeans/NumericField.ser. If the serialized form of the bean is not found, the instantiate() method attempts to create an instance of the class by name.[47]

Beans.isInstanceOf() and Beans.getInstanceOf() do the jobs of checking a bean’s type and casting it to a new type. These methods were intended to allow one or more beans to work together to implement “virtual” or dynamic types. They are supposed to allow beans to take control of this behavior, providing different “views” of themselves. However, they currently don’t add any functionality and aren’t widely used.

Working with Serialized Beans

Remember the Juggler we serialized a while back? Well, it’s time to revive him, just like Han Solo from his “Carbonite” tomb in Star Wars. We’ll assume that you saved the Juggler by flipping on the Serialization property while working with the LearnJava1 class and that NetBeans, therefore, saved him in the file LearnJava1_juggler1.ser. If you didn’t do this, you can use the following snippet of code to serialize the bean to a file of your choice:

    // Serialize a Juggler instance to a file...
    import magicbeans.sunw.demo.juggler.Juggler;
    import java.io.*;
     
    public class SerializeJuggler {
       public static void main( String [] args ) throws Exception
       {
          Juggler duke = new Juggler();
          ObjectOutputStream oout = new ObjectOutputStream(
             new FileOutputStream("juggler.ser") );
          oout.writeObject( duke );
          oout.close();
       }
    }

Once you have the frozen Juggler, compile the following small application:

    //file: BackFromTheDead.java
    import java.awt.Component;
    import javax.swing.*;
    import java.beans.*;
      
    public class BackFromTheDead extends JFrame {
      
      public BackFromTheDead( String name ) {
        super("Revived Beans!");
        try {
          Object bean = Beans.instantiate(
            getClass().getClassLoader(), name );
      
          if ( Beans.isInstanceOf( bean, JComponent.class) ) {
            JComponent comp = (JComponent)
              Beans.getInstanceOf(bean, JComponent.class);
            getContentPane().add("Center", comp);
          } else {
              System.out.println("Bean is not a JComponent...");
          }
        } catch ( java.io.IOException e1 ) {
          System.out.println("Error loading the serialized object");
        } catch ( ClassNotFoundException e2 ) {
          System.out.println(
             "Can't find the class that goes with the object");
        }
      }
      
      public static void main(String [] args) {
        JFrame frame = new BackFromTheDead( args[0] );
        frame.pack();
        frame.setVisible(true);
      }
    }

Run this program, passing the name of your serialized object as an argument and making sure that our magicbeans.jar file is in your classpath. The name should not include the .ser extension in the name; the Beans.instantiate() method adds this automatically in its search for the serialized or class version. The juggler should spring back to life, juggling once again as shown in Figure 22-9.

The restored Juggler

Figure 22-9. The restored Juggler

In BackFromTheDead, we use Beans.instantiate() to load our serialized bean by name. We then check to see whether it is a GUI component using Beans.isInstanceOf(). (It is, because the Juggler is a subclass of java.awt.Component.) Finally, we cast the instantiated object to a Component with Beans.getInstanceOf() and add it to our application’s JFrame. Notice that we still need a static Java cast to turn the Object returned by getInstanceOf() into a JComponent. This cast may seem gratuitous, but it is the bridge between the dynamic beans lookup of the type and the static, compile-time view of the type.

Everything we’ve done here could be done using the plain java.io.ObjectInputStream discussed in Chapter 12. But these bean management methods are intended to shield the user from details of how the beans are implemented and stored.

One more thing before we move on. We blithely noted that when the Juggler was restored, the bean began juggling again. This implies that threads were started when the bean was deserialized. Serialization doesn’t automatically manage transient resources such as threads or even loaded images, but it’s easy to take control of the process to finish reconstructing the bean’s state when it is deserialized. Have a look at the Juggler source code (provided with the examples) and refer to Chapter 12 for a discussion of object deserialization using the readObject() method.

Runtime Event Hookups with Reflection

We’ve discussed reflection largely in terms of how design tools use it to analyze classes. Today, reflection is frequently finding its way into applications to perform dynamic activities that wouldn’t be possible otherwise. In this section, we’ll look at a dynamic event adapter that can be configured at runtime.

In Chapter 16, we saw how adapter classes could be built to connect event firings to arbitrary methods in our code, allowing us to cleanly separate GUI and logic in our applications. In this chapter, we have seen how NetBeans interposes this adapter code between beans to do this for us.

The AWT/Swing event model reduces the need to subclass components to perform simple hookups. If we start relying heavily on special adapter classes, we can quickly end up with as many adapters as components. Anonymous inner classes let us hide these classes, but they’re still there. A potential solution for large or specialized applications is to create generic event adapters that serve a number of event sources and targets simultaneously.

The java.beans.EventHandler is a dynamic event dispatcher that simply calls methods in response to events. What makes the EventHandler unusual in Java is that it is the first standard utility to use reflection to allow us to specify the method by name. In other words, you ask the EventHandler to direct events to a handler by specifying the handler object and the string name of the method to invoke on that object.

We can use the create() method of EventHandler to get an adapter for a specified type of event listener, specifying a target object and method name to call when that event occurs. The target object doesn’t have to be a listener for the particular event type or any other particular kind of object. The following application, DynamicHookup, uses the EventHandler to connect a button to a launchTheMissiles() method in our class:

    //file: DynamicHookup.java
    import javax.swing.*;
    import java.awt.event.*;
    import java.beans.EventHandler;

    public class DynamicHookup extends JFrame {
      JLabel label = new JLabel( "Ready...", JLabel.CENTER );
      int count;

      public DynamicHookup() {
        JButton launchButton = new JButton("Launch!");
        getContentPane().add( launchButton, "South" );
        getContentPane().add( label, "Center" );
            launchButton.addActionListener(
                    (ActionListener)EventHandler.create(
                            ActionListener.class, this, "launchTheMissiles"));
      }
      public void launchTheMissiles() {
        label.setText("Launched: "+ count++ );
      }

      public static void main(String[] args) {
        JFrame frame = new DynamicHookup();
            frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.setSize(150, 150);
        frame.setVisible( true );
      }
    }

Here, we call the EventHandler’s create() method, passing it the ActionListener class, the target object (this), and a string with the name of the method to invoke on the target when the event arrives. EventHandler internally creates a listener of the appropriate type and registers our target information. Not only do we eliminate an inner class, but the implementation of EventHandler may allow it to share adapters internally, producing very few objects.

This example shows how we would call a method that takes no arguments—but the EventHandler can actually do more, setting JavaBeans properties in response to events. The following form of create() tells EventHandler to call the launchTheMissiles() method, passing the source property of the ActionEvent as an argument:

    EventHandler.create(
       ActionListener.class, target, "launchTheMissiles", "source")

All events have a source property (via the getSource() method), but we can go further, specifying a chain of property “gets” separated by dots, which are applied before the value is passed to the method. For example:

    EventHandler.create(
       ActionListener.class, target, "launchTheMissiles", "source.text")

The source.text parameter causes the value getSource().getText() to be passed as an argument to launchTheMissiles(). In our case, that would be the label of our button. Other forms of create() allow more flexibility in selecting which methods of a multimethod listener interface are used as well as other options. We won’t cover every detail of the tool here.

How it works

The EventHandler uses the java.lang.reflect.Proxy, which is a factory that can generate adapters implementing any type of interface at runtime. By specifying one or more event listener interfaces (e.g., ActionListener), we get an adapter that implements those listener interfaces generated for us on the fly. The adapter is a specially created class that delegates all the method calls on its interfaces to a designated InvocationHandler object. See Chapter 1 for more information about proxy classes.



[47] This feature would seemingly be applicable to XML-serialized Java beans using the XMLOutputStream as well, but it is not currently implemented for them. This is another sign that the JavaBeans APIs have stagnated.

Get Learning Java, 4th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.