BeanContext and BeanContextServices

So far we’ve talked about some sophisticated mechanisms for connecting Java Beans together at design time and runtime. However, we haven’t talked at all about the environment in which JavaBeans live. To build advanced, extensible applications we need a way for Java Beans to find each other or “rendezvous” at run time. The java.beans.beancontext package provides this kind of container environment It also provides a generic “services” lookup mechanism for Beans that wish to advertise their capabilities.

Beans that implement the BeanContextChild interface (or a subinterface) are passed a reference to the container’s BeanContext, which is the source for all container-related information. The BeanContext implements many useful interfaces that describe the Bean environment. (To use it, cast it to the appropriate interface.) The two interfaces that we will talk about here are Collection and BeanContextServices.

A bean collection is an object that implements the java.util.Collection interface. It can be used to enumerate the Bean instances in the container. A corresponding listener interface lets Beans find out when Beans are added or removed. The BeanContextServices interface provides a means for looking up Beans that provide services, either by the Bean’s class type or through a list of the currently available service Beans. There is a corresponding listener interface for being informed of new services and revoked services.

Doing much with these classes is a bit beyond the scope of this chapter, but we’ll show a simple example that lists the beans in the current container and the available services. Here’s the code; Figure 19.11 shows what the bean looks like.

//file: ShowContext.java
package magicbeans;
import javax.swing.*;
import java.beans.beancontext.*;
import java.util.*;

public class ShowContext extends JTabbedPane 
                         implements BeanContextProxy {
  BeanContext context;
  BeanContextServices services;
  JList servicesList = new JList(), beansList = new JList( );

  public ShowContext( ) {
    addTab( "Beans", new JScrollPane( beansList ) );
    addTab( "Services", new JScrollPane( servicesList ) );
  }

  private BeanContextChildSupport beanContextChild = 
    new BeanContextChildSupport( ) {

    public void initializeBeanContextResources( )  {
      context= getBeanContext( );
      try  {
        services = (BeanContextServices)context;
      } catch (ClassCastException ex){/*No BeanContextServices*/}

      updateBeanList( );
      updateServicesList( );

      context.addBeanContextMembershipListener( 
        new BeanContextMembershipListener( ) {
          public void childrenAdded(
            BeanContextMembershipEvent e){
            updateBeanList( );
          }
          public void childrenRemoved(
            BeanContextMembershipEvent e){
            updateBeanList( );
          }
        } );
      services.addBeanContextServicesListener( 
        new BeanContextServicesListener( ) {
          public void serviceAvailable( 
                BeanContextServiceAvailableEvent e ) {
            updateServicesList( );
          }
          public void serviceRevoked( 
                BeanContextServiceRevokedEvent e ) {
            updateServicesList( );
          }
        } );
    }
  };

  void updateServicesList( ) {
    if ( services == null )
      return;
    Iterator it = services.getCurrentServiceClasses( );
    Vector v = new Vector( );
    while( it.hasNext( ) )
      v.addElement( it.next( ) );
    servicesList.setListData( v );
  }
  void updateBeanList( ) {
    Iterator it = context.iterator( );
    Vector v = new Vector( );
    while( it.hasNext( ) )
      v.addElement( it.next( ) );
    beansList.setListData( v );
  }

  public BeanContextChild getBeanContextProxy( ) {
    return beanContextChild;
  }
}
Reporting on a bean collection

Figure 19-11. Reporting on a bean collection

The purpose of the BeanContextChild interface is to flag the bean as a child of the container and allow it to receive a reference to the BeanContext. But if you look at the code, you won’t see any BeanContextChild. Instead, you see that our example implements something called BeanContextProxy . What’s the story? Where is our BeanContextChild?

The easiest way to answer this question is to think about some of the design decisions that must have gone into the beancontext package. BeanContextChild isn’t an extremely complicated interface, but it still has a half-dozen or so methods, and Sun’s engineers didn’t want to oblige Bean authors to implement all of those methods whenever they wanted an object that implements BeanContextChild. There’s an obvious solution to this problem that has been used successfully elsewhere in Java: creating an adapter class that implements BeanContextChild, and letting the Bean author override only those methods that he or she is interested in. This adapter class is called BeanContextChildSupport—but it solves one problem only to create another.

If we want an object that implements BeanContextChild, we can certainly extend BeanContextChildSupport, but that’s ugly: it forces all of our Beans that need to know about the context to extend a particular class, which severely limits your design flexibility.

A better solution is to introduce another interface, BeanContextProxy, with a single method, getBeanContextProxy( ) , that returns a BeanContextChild. If we implement BeanContextProxy, the Bean’s container will call getBean-ContextProxy( ) to get a child object for us. We can then implement getBeanContextProxy( ) easily, using a private anonymous inner class that extends BeanContextChildSupport, and override only the methods that are important to us. In our private instance, we override one method: initialize-BeanContextResources( ). This method gives us a hook on which to hang any code that needs to run after the container has given us our reference to the BeanContext. Within this method we can safely ask for the BeanContext with the getBeanContext( ) method and use it to look up Beans or services. There are other ways around this problem, but the one described here is as effective as any, and requires minimal work.

Once we have a BeanContext, we can use it to look at the Beans and services that are available in our container. Getting the list of Beans available is simple: we just call the iterator( ) method of the bean context to get an Iterator that lets us extract references to the Beans. We do this in a helper method called updateBeanList( ) . Likewise, getting a list of the services available requires casting the BeanContext to a BeanContextServices object, calling getCurrent-ServiceClasses( ) to retrieve an Iterator that lists the services, and extracting the services from the Iterator. Again, we use a helper method called updateServicesList( ) to do most of this work. Finally, the Beans and services available can change at any time, so we register with the BeanContext as a BeanContextMembershipListener to find out about changes to the Beans, and as a BeanContextServicesListener to find out about changes to the services. BeanContextMembershipListener requires two methods, childrenAdded( ) and childrenRemoved( ), both of which handle a BeanContextMembershipEvent. Likewise, BeanContextServicesListener requires the methods serviceAvailable( ) and serviceRevoked( ), which handle (respectively) BeanContextServiceAvailableEvent s and BeanContextServiceRevokedEvents.

Whew! That was a dense example. But there’s really not much to it when you pick it apart. Next we’ll move on a bit and talk about some additional JavaBeans-related APIs.

Get Learning Java 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.