Home
Email
Butnz!

Experiences with JavaBeans

As a committed Java hacker, I decided it was time to learn about JavaBeans. So for several weeks I printed out every bit of documentation I could find and devotedly read it on the bus on the way to work and back. It somehow left me feeling unsatisfied, as if some documentation was missing in my life. So this page is all the stuff I wish I had been told.

What's a Bean?

The documentation denies that beans are anything special, and states that you've probably written one already. Well that's true to some degree, but really you don't feel like you've written a bean until you can drop one into the BeanBox. In my opinion, here are the important things you need to do to make a Bean:

Use the JDK1.1 event model. This includes generating your own events.
Put the code into a .jar with a manifest. This is easier said than done.
Write a BeanInfo class to assign icons to the Bean.

You'll also need to understand exactly where and how to use Beans. I'm not sure that I do yet.

Making a Manifest

The manifest file is a list of stuff that goes into your jar file listing what's in the jar. It also has an important function of telling the BeanBox which classes in the jar are supposed to be beans. The manual blithely says that when you make a jar file you can give jar a manifest file of information to add to the manifest which is added to the jar. Well it didn't work for me. I wrote a small manifest file like this:
Name: friendless/awt/SpinControl.class
Java-Bean: True
However that information was not added to the manifest in the jar at all, and the BeanBox kept complaining that I hadn't specified anything in the jar as being a bean. Instead, what I did was made the jar, extracted the manifest.mf using Moa (see Resources below), and edited that manifest by putting "Java-Bean: True" in for every Bean class. Then I used that version in my make command. If any new files were created, they would be automatically added to the manifest which was added to the jar. If any files were deleted, I had to take their entries out of "my" copy of the manifest. The manifest in the jar now had the beans marked appropriately.

Here's the batch file I use to make my beans. This is used to make the SpinControl and SpinButtons beans.

set CLASSPATH=.;k:\jdk112\lib\moa.zip
del friendless\awt\*.class
del *.jar
javac -deprecation friendless\awt\SpinControl.java
javac -deprecation friendless\awt\SpinButtons.java
javac friendless\awt\SpinControlBeanInfo.java
javac friendless\awt\SpinButtonsBeanInfo.java
jar cvfm SpinControl.jar Manifest.mf friendless
copy SpinControl.jar \BDK\jars
I had to set the CLASSPATH to not include my standard directories, to make sure that all code for the Bean was in the friendless subtree under the current directory - no point making a jar file that was missing required classes because they were elsewhere on the machine. I use the -deprecation flag to make sure I'm not falling back into JDK 1.02 habits. Note that the BeanInfo classes have to be compiled separately, because they aren't used in the Bean classes and so don't get compiled by default. "jar cvfm" means "create the jar verbosely given the filename and the manifest file". "friendless" is the directory that all code lives under - jar will archive the entire directory subtree. I had to start at that directory, because the Beans are in package friendless.awt, and the filenames in the jar wouldn't match the package names otherwise. BTW this makefile puts the source code in the jar as well, which is fine for me, but maybe you wouldn't want to do that.

BeanInfo

The big question on my mind when I started was "how does Java find the BeanInfo" for a bean? The answer is that when it finds a Bean called "XXXX.class", it looks for "XXXXBeanInfo.class". So if you stick the BeanInfo class in the same package as the Bean, it will find it for you. So how do you make a BeanInfo class? Copy this (friendless.awt.SpinButtonsBeanInfo.java):
package friendless.awt;

import java.awt.Image;
import java.beans.*;

public class SpinButtonsBeanInfo extends SimpleBeanInfo {

  public Image getIcon(int iconKind) {
    java.awt.Image img;
    if (iconKind == BeanInfo.ICON_COLOR_16x16) {
        img = loadImage("SpinButtonIco16.gif");
	  }
      else if (iconKind == BeanInfo.ICON_COLOR_32x32) {
	  img = loadImage("SpinButtonIco32.gif");
	  }
	else if (iconKind == BeanInfo.ICON_MONO_16x16) {
	  img = loadImage("SpinButtonIcoBW16.gif");
	  }
	else if (iconKind == BeanInfo.ICON_MONO_32x32) {
	  img = loadImage("SpinButtonIcoBW32.gif");
	  }
      else img = null;
    return img;
    }
}
There are other things you can do with BeanInfo, but really it gets terribly complicated, and I spent a day just figuring this stuff out, so I don't understand them yet. This BeanInfo just returns icons to make your Bean list nicely in the BeanBox. It's otherwise useless. Notice the method loadImage(), which takes the filename of an image and returns the java.awt.Image for you. More on that later, but you must keep those images in the same directory as the .class files that refer to them, and then they get put in the jar. Here are those images, in case you are wondering:


Making the icons is one of the hardest parts of making the Bean! I used PaintShop Pro and LView Pro.

Using Images in Beans

I have always found one of the hardest parts of writing a GUI component in Java to be arranging for it to be able to access any images it needs. If you're in an applet, you need to do getImage(getCodeBase(),"filename.gif"), whereas in an application, you need to do Toolkit.getDefaultToolkit().getImage("filename.gif"), and it seems to be fairly pot-luck as to which directory your application looks in for that filename. So with the use of Beans and jar files, I thought my image-finding hassles would be over. Well maybe they are now, but it was a hard slog!

My SpinButtons bean uses three images: . Yes, this means you cannot customise the Bean to your own colours, maybe I will do it that way in a future version. The problem is, how do I load those images from the jar file? I tried calling loadImage(), but it is only a method in SimpleBeanInfo. After some investigation, I discovered that a Bean can be an Applet, and you can load images like an applet does. This sounded a mite suspicious (like, where does it get its AppletContext from?) but I coded it up that way and got it working in the BeanBox and in an application (see below).

Then I proceeded to write my SpinControl class, which has some SpinButtons which control the value of a number. As I was writing the SpinControl to be distributed in the same jar as the SpinButtons, I figured it could just use the SpinButtons class without having to go through the Bean instantiation procedure. Although it compiled, the code would not work. The problem was that when I tried to instantiate the SpinButtons control from the SpinControl control, getCodeBase() in the SpinButtons control was null. If you read the documentation for java.beans.Beans.instantiate(), you'll notice that it says that the Beans instantiation procedure creates a fake AppletContext and AppletStub if the Bean is an instance of java.applet.Applet. So it will work if your Bean is an Applet itself, but if you want create that Bean inside another Bean, you have to make the fake AppletContext and AppletStub, or use the Beans instantiation procedure.

I decided that this was all getting a little too far removed from sensible coding practice, and determined to find the One True Way. Remember the reason I made SpinButtons an Applet in the first place was to be able to load images, so I really needed to solve the image-loading problem better. I knew a couple of the samples I downloaded from the net used images, so I scrounged around in the code and came up with this:

  public Image loadImage(String resourceName) {
    try {
        java.net.URL url = getClass().getResource(resourceName);
        java.awt.Toolkit tk = java.awt.Toolkit.getDefaultToolkit();
        return tk.createImage((java.awt.image.ImageProducer) url.getContent());
        }
      catch (Exception ex) {
        return null;
        }
    }
This is from Bob Leurck's HoverLabel stuff, thanks Bob. To tell you the truth, this code doesn't strike me as blindingly obvious. First it builds the URL of a resource by asking the ClassLoader (via class Class) to find it. I guess you can give the name of the resource relative to the package of the current class, or maybe even absolutely. Then it calls the Toolkit to create the image from the data contained in the URL. It's easy when you know how! The URLs generated look like this, both when running in the BeanBox and when running in an application.
simpleresource:SIMPLEBeanBox/+/friendless/awt/updown.gif

So, by getting the ClassLoader involved in the loading of images, they can be easily loaded from the jar file. Making the Bean an Applet was a Bad Idea, and you shouldn't do it, although that's how Sun's Juggler example is implemented. I don't understand why JavaSoft haven't made loadImage a standard function in class Component or somewhere like that. Royalties to Bob please.

Using Beans in Applications

So I have navigated many treacherous rapids and been able to package several Beans with images into a jar, and got them to run in the BeanBox. That's lovely, but the BeanBox is not really very useful otherwise. What I need to do is plug my Beans into an application. There are two options here: to use the jar file as a zip file, in which case it must be uncompressed; or to use the jar file in the compressed format and get at it through Beans interfaces. The first is easy, so I decided to do the second.

the real difficulty is that if you want to use a SpinButtons in your program, but you don't have access to the SpinButtons class at compile-time, you can't actually mention the word SpinButtons. You have to get the JavaBeans infra-structure to create you a Bean from a given jar file, and give it back as a Component. This is how you do that:

  public static Component getComponent(String jarFile, String classname) {
    JarInfo info = (JarInfo)infos.get(jarFile);
    if (info == null) {
        try {
            JarLoader loader = new JarLoader(jarFile);
            info = loader.loadJar();
            }
        catch (Exception e) {
            System.out.println(e);
            return null;
            }
        if (info != null) infos.put(jarFile,info);
        }
    if (info == null) return null;
    Object o = info.getInstance(classname);
    if (o instanceof Component) {
        Component c = (Component)o;
        return c;
        }
      else {
        System.out.println(classname + " is not a component.");
        return null;
        }
    }

  public static Component getSpinButtons() {
    return getComponent(SpinButtonJar,"friendless.awt.SpinButtons");
    }
I had to rip off the JarInfo and JarLoader classes from the BeanBox. Now this might seem like a silly game, but the JavaBeans documentation claims I need to know nothing about an object to begin using it, and what better way to know nothing about it than to have it not available at comple-time :-)?

I also wanted to register to receive action events from this object. Of course, I do this by calling addActionListener() in the object, but Component doesn't define that method. In fact, there is no interface which defines that method, so I cannot actually use instanceof to determine whether I can receive ActionEvents from an object. So I defined my own interface (ActionEventSource), and made the SpinButtons implement that interface. But that didn't work - although the Class object for the instantiated Bean swore blind that it implemented ActionEventSource, it refused to let me cast the Component to ActionEventSource. I noticed later that java.beans.Beans has a method called getInstanceOf, so for some reason it seems that casting doesn't work on Beans. Hmmm.

Fortunately there is an obscenely intrusive process call Introspection and Reflection in which a Bean tells you anything you want to know about it, including calling any public method you like. So this method uses reflection to call addActionListener in any object which implements it, no matter what class it is. Gosh, doesn't the code look like calling an IDispatch method in DCOM?!

  public static void becomeActionListener(Object o, ActionListener me) {
    Class c = o.getClass();
    Method m = null;
    try {
        Class[] argClasses = { Class.forName("java.awt.event.ActionListener") };
        m = c.getMethod("addActionListener",argClasses);
        Object[] args = { me };
        Object ret = m.invoke(o,args);
        }
    catch (Exception e) {
        System.out.println(e);
        }
    }
I packaged these two useful functions together into a class called BeanBroker.

So now I can use BeanBroker to worry about actually getting to the Beans, while I just write the code. Here's the body of addNotify() for the application:

    super.addNotify();
    setLayout(new GridLayout(1,2,4,4));
    add(val = new Label());
    add(spin = BeanBroker.getSpinButtons());
    BeanBroker.becomeActionListener(spin,this);
    val.setText(""+i);
It creates some SpinButtons and makes itself an ActionListener of them. In its actionPerformed() method, it increments or decrements i according to whether the up or down button was pressed.
  public void actionPerformed(ActionEvent evt) {
    if (evt.getActionCommand().equals("UP")) {
        i++;
        }
      else if (evt.getActionCommand().equals("DOWN")) {
        i--;
        }
    val.setText(""+i);
    }
Here's UseBeans.java and MainWindow.java which is a generic Frame that holds whatever you like and responds to the window kill button.

Further Thoughts

So, I have created a couple of Beans and tortured them in every way possible. What next?

Property Stuff

Well the SpinControl control needs some work on its properties. I went through and turned its entire innards into properties. However, I have done a bad job of it. The Bean should constrain its properties so that the lower limits are lower than the higher limits. It can do this using PropertyVetoExceptions. Maybe I should also add support for PropertyChangeListeners, in case the SpinControl is used in some context that wants to look at it. Also I should pay attention to System Properties, generate events when the value changes, and not be so ugly. In short, it's easy to be a Bean, but to be a good Bean is Hard Work.

Persistence

A Bean should also support persistence. This means the Bean needs to be serialisable, by providing parameterless constructors for each class it uses, and marking those values which need not be saved as transient. The Bean shouldn't assign default values to its properties in addNotify(), because by time addNotify() is called, the values from the serialised version have been set, and those properties would then be uncustomisable.

Stuff

Here's the stuff mentioned in this article.

Resources

If you have been thinking about getting into Beans, you'll probably have collected some stuff already. Here's some that I have found particularly useful. Sorry for any sites mentioned that I don't have links to at the moment, I don't have my links file here.
The Beans Development Kit
This is from JavaSoft. It includes the BeanBox, which is a sort of a Bean development environment. It also comes with source code, which is dang useful when you want to do something with a bean and you don't know how. It also has Beans API documentation, which is faintly useful in the nature of Javadoc documentation.
The Coffee Grinder
A very good site which has links to some documentation I haven't found elsewhere, and some sample Beans.
Moa
Moa is a visual utility for examing jar files. It's not quite drag and drop easy, but it's better than fiddling with the command line jar program. By the way, jar is modelled on the the Unix tar, and if I'd realised that sooner I would have used it longer. Jar files are sort of in the same format as .zip files, but I couldn't get WinZip to successfully extract a manifest for me, so best use Moa or jar rather than WinZip. Moa is also capable of making a .ser (serialised object) file from a Bean, which I don't quite understand the use of yet.
Mocha
Mocha is a Java decompiler, i.e. it takes a class file and produces Java source code from it. I have had it for a while, but didn't bother to install it because I didn't think it could do a very good job, but IT'S ABSOLUTELY BRILLIANT! Fair dinkum, the source code it produces are better than the original source in some cases. It doesn't seem to preserve line numbers exactly, but it is REALLY REALLY USEFUL. It's also easy to install and use.
"Java Beans" by Lawrence Rodriguez
David Wood's JavaJolt column
A little superficial, but he seems to regularly find the odd corners of Java that others miss.

This page hosted by Get your own Free Home Page.
This page uses Verdana fonts which Windows users can download from Microsoft for free.
Don't know where I got the background pattern from, but if you recognise it as one of yours, let me know and I'll link to you here.

Friendless 1