Beans are really no different than any other java class, one simply takes advantage of all of the capabilities classes have been allowed. For example, a bean can generate events in the same way that an AWT component, such as Button generates ActionEvent events. Events are simply for notifying others when something is happening; we have always had the capability to customize events by inheriting from the class java.util.EventObject
The delegation-event model of AWT, introduced with Java 1.1, demonstrates the Beans event model (as discussed earlier). It has three parts:
Basically, a Bean that wants to generate events needs a way to keep track of interested event targets. In the delegation event model, the event mechanism is broken up conceptually into event dispatch and event handling. Event dispatch is the responsibility of the event source; event handling is the responsibility of the event listener. Any object that wants to know when an event is fired by a bean can tell the bean it wants to be informed about particular events. In other words, an event listener registers interest in an event by calling a predetermined method in the event source.
Anyone can register an EventListener with a Component, provided the Component understands the event set. (For instance, you cannot register an ActionListener with a TextArea, but you can with a TextField). When something happens within the Component, it notifies any listener(s) by sending each an EventObject, through the appropriate method of the listener.
Remember VetoableChangeListener and its vetoableChange(PropertyChangeEvent evt) method? In this event handler we had a dialog box come up that said the option was not allowed. Remember the Voter bean in the examples in the BeanBox? Here, by default it rejected all vetoableChange requests, though you could change its vetoAll property to "true" and it would allow changes. For your own defined events and event handlers, you can allow whatever you want to allow.
Custom event
A bean defines an event if it provides methods for adding and removing event listener objects from a list of interested listeners for that event.
Once again, patterns are used in the signature of the method names. The pattern of the method's signature is detected by Java's new introspection mechanism, which can tell what events the source will generate from the name of the registration methods, together with the type of the arguments of the registration methods. For a detailed explanation see the Beans Spec, section 6.5 Event Listener Registration. (And some changes at Getting Listeners from JavaBeans)
The general pattern for event generation capabilities recognized by Java's introspection mechanism is as follows:
If the button bean wants to be an event source, it must provide two methods that can be called by interested objects. One method adds the caller to the list of listeners who are notified when the event occurs. The other method removes the caller from the list of interested listeners.
where TYPE is replaced by the class name of the particular event listener.
The bean button needs a way to keep track of all of the listeners who might register to receive notification of the TYPE events. In our previous examples we were given the PropertyChangeSupport and VetoableChangeSupport classes). If we customize our events, we will need to implement these ourselves.
Since TYPE is vague, before I go on, let us use an example from one of the tutorials ... from the start. Remember, we have events, listeners, and objects that fire the events.
public class java.util.EventObject
extends Object implements java.io.Serializable {
public java.util.EventObject (Object source);
public Object getSource();
public String toString();
}
Although you can create EventObject instances directly for your Bean
events, design pattern guidelines require you to subclass EventObject
so you have a specific event type. For example,
to define an event for an employee's hire date, you could create a
HireEvent class.
public class HireEvent extends EventObject {
private long hireDate;
public HireEvent (Object source) {
super (source);
hireDate = System.currentTimeMillis();
}
public HireEvent (Object source, long hired) {
super (source);
hireDate = hired;
}
public long getHireDate () {
return hireDate;
}
}
Notice also the read-only properties of EventObjects
public interface HireListener
extends java.util.EventListener {
public abstract void hired (HireEvent e);
}
The method signature for an event-listener method is
void eventOccurranceMethodName (EventObjectType evt)You can also include a throws clause that lists any checked exceptions that might be thrown when this method is invoked.
public synchronized void addListenerType(ListenerType l); public synchronized void removeListenerType(ListenerType l);Multiple listeners can be registered with a source for a given set of events. When an event occurs it will be fired to all of the event-listener objects. For our own defined EventObjects, the event source needs to maintain the list of listeners itself (i.e., we need to supply the Support class). This is called multicast event delivery. The entire code to do this is:
private Vector hireListeners = new Vector();
public synchronized void addHireListener (HireListener l) {
hireListeners.addElement (l);
}
public synchronized void removeHireListener (HireListener l) {
hireListeners.removeElement (l);
}
If you are using AWT components, AWT events already have this behavior. Maintaining listeners is only necessary for new event types, or adding listeners where they previously were not (ActionEvent within a Canvas). Note that the JavaBeans specification does not dictate the event firing order for multicast event sources. It is up to the implementor of the source object to decide the order of notification.
The same holds true if an event listener is registered more than once, or if a non-existent event listener is unregistered. What happens is implementation dependent and should be documented. The ultimate user should know if it will be ignored, if an exception will be thrown, etc., so they can deal with it.
For example, if you want to permit only one listener (unicast), you have the addListenerType method throw the java.util.TooManyListenersException exception when adding a second listener(and you do not need a Vector).
OK, once the event happens, it is necessary for the event source to notify all the listeners. For the hiring example, this would translate into a method like the following:
protected void notifyHired () {
Vector l;
// Create Event
HireEvent h = new HireEvent (this);
// Copy listener vector so it won't change while firing
synchronized (this) {
l = (Vector)hireListeners.clone();
}
for (int i=0;i<l.size();i++) {
HireListener hl = (HireListener)l.elementAt (i);
hl.hired(h);
}
}
You have to call the method directly when the triggering event happens.
Finally, remember to synchronize the add/remove methods. You cannot assume that the event notification is being delivered on the same thread that created your listening object. In fact, you should assume that this is not the case. Under these conditions, it is necessary for you to protect your code from multithreaded access by using the synchronization constructs
Don't forget to look at other events and listeners in the AWT package java.awt.event so that you can extend and enhance the provided interfaces in your code. An example is seen in "Java in a Nutshell", page 184 with their
This bean is very interesting - look at the code. Control trace:
Now, another example...which does implements Serializable and I don't know how it can without doing something additional. Why? Is it because there is really nothing there you would save so any instance of this class would have been transient anyway? What about the instance of Thread?... And besides, it does say implements Serializable. Seems to me that any class using this would have to get the instance of it running again after it reads the Objects back. So it is the problem of the containing class? To say it implements Serializable indicates to me that Thread should... and it doesn't. Hmmm, any suggestions? thoughts?
At any rate, the Core Java book had great examples. In this case we want to look at
Demultiplexing and Event Adapters "Developing Java Bean", Englander
Because a listener object can receive event notifications from multiple sources, and each of these might want different actions to be performed, it is a good idea to be a multiplexing event receiver. Also, we might find it appropriate to have different methods in a class to deal with events from different instances of the class, so if we have one Listener used by all event sources, it needs to have some identification of these sources. Adapters are sometimes a good technique for decoupling an event listener from an event source. Some ways to deal with interface(s) and a lot of objects using it (so far):
We, the everyday programmer, can access this information as well by using the "metaclass" of java.lang.Class. Specifically, this class is a class that is used to get information about classes. Each Object (classes and instances) used in a given program has a corresponding instance of java.lang.Class associated with it.
For example, the java.lang.Class class contains a static method called forName(). This method takes a string parameter containing the fully qualified class name and returns an instance of java.lang.Class. If the class does not exist, the exception java.lang.ClassNotFoundException will be thrown.
Finally, the java.lang.reflect.Method class has a method named invoke() which is used to invoke a method. It takes the target Object as the first parameter, and an Object array as the second parameter. This parameter is used to pass the parameters to the method being invoked. (If a parameter is not an Object, you can use the fact that each primitive data type has a wrapper class with .TYPE static field. Example: float is the value of java.lang.Float.TYPE.
Example: We will use: the API Class getMethod
and API Button setLabel
and the API Method invoke
try
{
// get the button class
java.awt.Button theButton =new java.awt.Button("Sample");
Class theClass = theButton.getClass();
//get the string parameter class
java.lang.Class paramClasses[] ={java.lang.String.class};
// get the method
java.lang.reflect.Method mthd=theClass.getMethod("setLabel", paramClasses);
//now invoke it, changing the label to "Did It"
Object param[] = {new String("Did It")};
mthd.invoke(theButton, param);
}
catch (java.lang.NoSuchMethodException e) {}
catch (java.lang.IllegalAccessException e) {}
catch (java.lang.IllegalArgumentException e) {}
catch (java.lang.reflect.InvocationTargetException e) {}
This technique allows us to create an adaptor that uses reflection to find
the method that handles events. Now, we can use the technique to create
an adaptor that adapts the interface to any arbitrary target object, and that
can handle multiple object instances as event sources.
The example provided is one with object sources Thermometer and we want a GenericTemperatureAdaptor to implement TempChangeListener for the event type of TempChangedEvents.
The only requirement is that the signature of the methods that the adaptor will forward events to looks like:
When the adaptor receives a TempChangedEvent it will forward it to the method on the target with the name passed into this parameter. The adaptor uses a hash table to store these mappings.
See the Generic Temperature Adapter and the Thermometer class definitions. Interesting, eh?!
Note that they did not handle all of the exceptions that could be thrown. Memory trace...how does one know when an exception is thrown by a class?
Pretty spiffy. I apologize for yelling about Java not having a way to pass methods (although I read somewhere that the developers thought such a thing was a bad idea...they thought that use of interfaces was a better technique, and that this capability was a by-product of having the Reflection package available). I, for one, have found this capability useful in past languages, as well as the capability to determine all of the instances of a class. OK, another example:
The same technique can be done with classes supplied by Java in the AWT package (e.g., Buttons, ActionEvents and ActionListeners). The following is an adaptor that will handle the ActionListener interface for multiple buttons, and will forward the ActionEvent to a method on the target object. Here's the code for the GenericButtonAdapter and an example applet using it. The applet has three buttons, each with different handler methods exampleapplet2.java
This also leads to a nice place for a little more discussion on serialization, since this class uses a hash table. Since we didn't discuss this in the section on serialization, I want to take a few minutes here, as not to detract from this page, see this page.
Methods are operations for others to interact with a Bean. Basically, anything can call any public method of a Bean; usually the set of public methods defined by the class will map directly to the supported methods for the Bean. Beans receive notification of events by having the appropriate method called on them by the event source.
Besides all the methods required for each property and event of a Bean, you usually create support methods which accept no arguments or an argument of an event you listen for. That way, a builder application can inspect a class quickly for a list of the appropriate methods it can connect to. Non-public support methods may also be available. However, public methods with other parameter patterns are usually the result of a bad design pattern, and not easily connectable from a builder application. For instance, if you always pass along a time-stamp with an event, you should probably subclass the event, add a time-stamp attribute, and pass the subclass around. This results in a simplified design and easier maintainability.
As stated, bean methods are available for anyone to call by just making each public. However, you can restrict which methods are visible to the Bean builder/integration tool by providing a getMethodDescriptors method along with your Bean's BeanInfo. Every Bean can provide a supporting BeanInfo class to customize a Bean's appearance to an integration tool. More on BeanInfo