Make a sweep with clean beans

Design rules that will keep your beans clean and your apps secure

Classes are basic structures in an object-oriented language such as Java. Apart from syntactic and type constraints, there are no limitations as to what is a valid class and what is not. However, a class can be reused, via subclassing, and automatically processed by an application that dynamically imports objects. For these uses, the general class definition is too weak and must be refined.

Let there be beans

Beans were developed to meet this shortcoming. Upon automatic processing, beans present a predictable interface by setting more constraints on classes. If you reuse a class, you should also work with beans, because they are cleaner than general classes.

At a minimum, a bean must be serializable, and be a public class with a public zero-argument constructor. Other, optional, characteristics that improve a bean's interoperability are the ability export values, called properties, via accessors (get/set methods), and the ability to notify listeners using an event model (like AWT 1.1).

The introspection mechanism figures out a bean's events and properties according to the signature of its accessors, such as the pattern by which these methods access the events and properties. For example, the two methods String getFoo() and void setFoo(String arg) identify a string property named foo.

The bean package also specifies the BeanInfo class that describes the bean class. BeanInfo assists the code introspector while the bean is dynamically processed.

Beans define a component framework and are quite autonomous. The autonomy of a bean makes reuse and automatic processing easier, because the constraints on a bean and on its properties require that its interface be more predictable than a general class. A bean is created using the zero-argument constructor, and is customized by invoking the setter methods. Without the bean's constraints, you have to choose between the available constructors to create an instance, and find the right methods to call among all the methods provided by the class. However, even having these constraints in place is still not enough for true interoperability.

Take applets, for example. They run safely inside a browser because security constraints prevent them from damaging their hosts and user environments. In contrast, there is no such security model for beans that maintains their behavior. If a bean acts poorly, it endangers the applications that integrate it. Integrating a component safely into a program is a long-standing problem in software development. Beans do not solve it, but they can help.

Rules for clean beans

In this article, I would like to propose the concept of the clean bean, a bean which meets a minimum quality level, and which can thus be integrated and used safely in a dynamic environment.

Figure 1. Class types

Figure 1 presents the different class types. Only some classes can be considered beans, and only some beans merit the clean label. The following are the requirements that a bean must fulfill in order to be considered clean:

  1. The clean bean state is entirely defined by its properties.

  2. Accessors are independent. In particular, accessor calls are commutative.

  3. Clean bean property values are either themselves clean beans, or associated with a java.beans.PropertyEditor (note that primitive values have PropertyEditor already built in).

  4. Event listeners must be exported as indexed properties.

  5. Clean beans are error proof.

  6. Clean beans are thread safe.

With this set of constraints, an application can trust the clean bean and use it without risking a drop in quality. Let's examine these clean bean requirements in detail.

1. The clean bean state is entirely defined by its properties

This constraint is essential. A clean bean cannot afford to have a hidden state that leads to differing behavior in identical usage situations.

Note that a bean's internal variables are compatible with this rule. The rule says that the bean state depends only on the property values. If the bean manages an internal state that is not related to the properties, then this state has no importance for the bean, though it may be used by the bean to double-check, to optimize, to cache, and so forth.

2. Accessors are independent

This rule generally states that a clean bean does not assume a standard policy for calling its accessors. In particular, there is no policy on how multiple accessors are invoked. For instance, accessor calls are

commutative

-- that is, there is no particular order in which they must be invoked.

Another good example is the synchronized call of several accessors. If the bean requires several accessors to be called in the same thread in order to work properly, then the properties are in some way dependent, revealing a design flaw somewhere.

Instead of dependent accessors, a better design pushes the dependence inside the value (gathering the dependent properties into one property, for example) using a suitable data structure as the value type. This data structure encapsulates the implicit dependence of the previous properties, taking advantage of the type-checking for good data use.

The independence of accessors improves security, because it suppresses hidden constraints that only appear in the documentation (e.g., "Remember to call setXXX() before setYYY(), or it will not work"). As a result, the rule saves you the difficulty of asserting such a constraint in the code.

3. Property values are closed

This rule is also essential, because it implies that

a clean bean stays in a world of clean beans.

If a property value is not itself a clean bean, it must be precisely described by a

java.beans.PropertyEditor

(see the bean documentation for details on

PropertyEditor

). This ensures that a bean box that manipulates a clean bean can offer a workable user interface for bean customization, one that works with

all

values.

Clean beans work recursively, and PropertyEditor provides a means (usually through string converters) to edit the property value. Other values must be processed as special cases, therefore, the blind exploration of a bean is defeated, and the user can take advantage of true interoperability.

To evaluate the need for this rule, imagine what happens to a bean that does not comply with it. An application that integrates the dirty bean has to face an alien value type. How can the integrating application work out the alien type? All it can do is deal with it as an Object, hoping that no more precision is needed for manipulating the value.

Default PropertyEditors are provided for the Java built-in types boolean, byte, short, int, long, float, and double, and for the classes java.lang.String, java.awt.Color and java.awt.Font. Note that char is missing from the list; this means that we should always provide a PropertyEditor for char, because it is a primitive type.

4. Listeners must be exported as properties

This rule meets a need that the original bean specification seriously lacks: it provides a way to know the current list of listeners. This was not thought to be necessary in the original bean design because Java serialization was the only service that needed this information, and it had special privileges to get it. For a clean bean, however, the listener list

must

be accessible, because it constitutes a part of the bean state.

The ability to retrieve the current listener list should be part of the pattern that identifies the bean events. In the meantime, we can only provide an explicit rule for encapsulating the listener lists as indexed properties.

5. Clean beans are error free

This rule, as utopian as it may seem, derives from the fact that beans must be trustworthy in order to interoperate well. It is very cumbersome to import a bean that fails to execute properly, so a clean bean should be

bug free.

Setters must check their parameters and throw IllegalArgumentExceptions to reject parameters, but should never fail later on a NullPointerException. Why? Because the illegal call exception demonstrates which parameter is faulty, providing an opportunity to correct it; other exceptions, which fail inside a bean, may not work. Think about third-party beans, shipped without source. How can you track back the faulty parameter from the failure without the source code?

On the other hand, you must be realistic. Setters need side effects to update a state or to execute actions. A clean bean should try to limit these side effects. The bean should at least still work correctly if methods are abused -- when a setter is called twice in a row, for example. This kind of defensive design is a good investment.

There is an open issue for property values returned by getters. Mutable objects (like java.awt.Point) or array values are subject to side effects because the caller can modify array elements or object states outside the bean. Getters should return a copy of the value. But cloning objects and copying arrays are expensive operations. Too much defensive programming will eventually ruin the bean's performance. The designer should find the right balance here. For this particular problem, I propose two APIs for clean beans, discussed in "Designing clean beans," below.

6. Clean beans are thread safe

Multithread programming is difficult. Most bugs are a nightmare to track down. Once again, you should be able to trust beans, which should react correctly in a multithreaded environment. An unsafe bean that is integrated into an application can make the whole application unsafe.

A bean can synchronize itself or its internal objects when needed and still be considered thread safe. But a clean bean should avoid synchronizing external objects directly, because this can lead to liveness problems. Remember, a clean bean is a black box that we trust because it behaves correctly. We certainly do not expect a clean bean to be responsible for the program freezing up. A good design will push a synchronized statement inside the object to be synchronized. The clean bean then invokes methods on the external object that wraps the synchronized statements.

The advantage of this design is that the scope of the synchronized area is cleanly defined, because it remains within the object. Each object is expected to synchronize only its internal objects. If a problem arises, it happens in the synchronized object, not in the owner bean. It is also easier to log and debug.

Cleaning beans

Here are several ways to make a dirty bean clean:

  • Set an accurate BeanInfo. If the original bean was nearly clean, this can sometimes be enough. Note that BeanInfo can discard properties, even if they comply with the bean specification, and register properties, even if their accessors do not follow the pattern. They can also enforce the third rule by providing a PropertyEditor for nonstandard values.

  • Wrap the bean into a clean bean, which delegates property accessors to the original class, cleaning them on the fly when needed, or creating high-level clean properties. This technique should work to fulfill at least the first four rules.

  • Subclass the bean to clean it. This should work to fulfill all the rules.

  • Rewrite the class to a clean bean version. This assumes that you have access to the bean's source code.

Which method you use to clean up your bean depends on the bean's context, how it is deployed, security issues, whether you have the source code, whether you can substitute a bean by a subclass, and so forth.

Designing clean beans

You can, in fact, allow a bean to have two APIs (see Figure 2). Remember that BeanInfo filters out properties that are not explicitly declared. So, define the clean API needed for interoperability in the BeanInfo class. To be more efficient, the dirty API is reserved for internal use of the package and declared in a Java interface. The dirty API is the interface the bean implements. The clean one is the API provided by BeanInfo. For example, below we define two properties named dirtyFoo and cleanFoo. Both are directly usable, but only cleanFoo is reachable from the bean world (from a bean box, for example).

  public class MyBean implements MyInterface { 
    ... 
  }
  public interface MyInterface {
    String getDirtyFoo();
    void   setDirtyFoo(String);
    String getCleanFoo();
    void   setCleanFoo(String);
  }
  // bean info for MyBean
  public class MyBeanBeanInfo extends java.beans.SimpleBeanInfo {
    // we keep only "cleanFoo" property
    public PropertyDescriptor[] getPropertyDescriptors() {
    PropertyDescriptor pds[] = null;
    try {
        pds = new PropertyDescriptor[] {
        new PropertyDescriptor("cleanFoo", MyBean.class),
            };
    } catch (IntrospectionException e) { 
        e.printStackTrace();
    }
    return pds;
    }
 }
Figure 2. Two APIs for clean beans are possible

Dirty accessors are still public, but not visible to the bean world because their properties are not declared in the BeanInfo class. The clean rule penalties are confined to external use of the bean, which is actually a reasonable price to pay. For example, an array value can have a clean getter that copies the array, and a dirty one that returns the array itself. The dirty getter is more efficient because it does not have the time and space overhead resulting from the copy, but it opens a safety hole, relevant to internal use only.

Here is another example. Assume there is a bean with a Hashtable property that maps a name to a URL. A naive implementation of the accessors may look like this:

1 2 Page 1
Page 1 of 2