Close

Java - JavaBeans components quick tutorial

[Last Updated: Jun 19, 2021]

JavaBeans specification defines reusable software components called beans. A developer can introspect other developers bean components and can integrate them into her/his own application.

There are certain conventions that we have to follow to define a valid JavaBean component.


Beans properties and getter/setters

Beans must have a default no-arg constructor.

Getters/setters should follow the strict naming convention. If the field name is abc then the getter/setter names should be getAbc and setAbc (notice the capitalization of first letter)

It's not necessary that there has to be a field for each property. A getter can return a property value based on some local evaluation.

Also in case of read-only properties, the bean doesn't have corresponding setters.



Indexed Properties

Indexed properties are arrays or lists.



Bound Properties

A bound property notifies listeners when it's value changes

The listeners and firing event is based on the observer (or Publish-Subscribe) pattern which is used to make the components communication decoupled from direct dependencies.



Constrained Properties

A constrained property is a special kind of bound property. When a constrained property is about to change in the setter method, the VetoChangeListeners are called. Any one of the listeners has a chance to veto (reject) the change, in which case changing the property is abandoned and it remains unchanged.



Bean methods and custom events

Bean is free to define methods other than property setters/getters. Any public method that is not part of a property definition is a bean method.

A bean can fire custom events too. A custom listener must be a subclass of java.util.EventListener



The package java.beans



BeanInfo

A bean can explicitly specify which properties, events, and methods that it supports by providing a class that implements the BeanInfo interface. It exposes 'introspection information' about the bean.


java.beans.SimpleBeanInfo provides a default support implementation which contains noop (no operation) methods.

We don't need to override all the methods from SimpleBeanInfo. We can pick and choose which information we want to provide and the rest (for the one still having noop) will be obtained by automatic analysis using low-level reflection.



How to associate a BeanInfo implementation with a bean

A BeanInfo implementation name must ends with BeanInfo e.g. MyBean -> MyBeanBeanInfo

Also MyBeanBeanInfo should be in the same package location where the MyBean resides. e.g. if com.example.beans.MyBean then bean info should be com.example.beans.MyBeanBeanInfo.


What are Descriptors?

All descriptor classes in java.beans package provide some introspection information regarding their corresponding artifact.

FeatureDescriptor class is the common base class for BeanDescriptor, PropertyDescriptor, IndexPropertyDescriptor, EventSetDescriptor, ParameterDescriptor and MethodDescriptor. FeatureDescriptor supports some common information shared by all it's sub classes.

Generally methods in these sub-classes return info about :
--java.lang.Class or types defined in java.lang.reflect package.
--Some other descriptor(s) types

PropertyDescriptor has methods to create PropertyEditor as well.


What is PropertyEditor?

This interface defines methods to change text to Java objects and vice versa so that it can be used to display or edit bean properties on a UI application or some UI builder/IDE.

Typically PropertyEditor implementation is defined and registered on per Java type basis. That means one Java type will have one PropertyEditor implementation.

One bean property is associated with one instance of PropertyEditor.


How to associate a bean with its PropertyEditor implementation?

For auto discovery, the implementation of the PropertyEditor should be in the same directory as corresponding bean and should follow same naming convention as BeanInfo, e.g. if com.example.beans.ABean then the editor should be com.example.beans.ABeanEditor.



PropertyEditorManager

Provides static methods for finding and registering PropertyEditors

Finding the instance by bean type:

PropertyEditor editor = PropertyEditorManager.findEditor(ABean.class);

Registering editor:

PropertyEditorManager.registerEditor(ABean.class, PropertyEditorImpl.class);

This overrides the editor which was auto-discovered as stated above.



PropertyEditorSupport

As PropertyEditor interface has a lot of methods to be implemented, this support class provides a default implementation. Our new editor can extend PropertyEditorSupport or we can use it as delegate.



Default PropertyEditors in JDK

JDK provides various editors by default but those are not in official API but are in com.sun.beans.editors package. Those editors are for following types: java.lang.Enum, java.awt.Color, java.awt.Font, java.lang.Boolean, java.lang.String, java.lang.Number



Introspector

This class provides standard way to get BeanInfo instance for a given Java Bean. We do that with the help of static methods of java.beans.Introspector

BeanInfo beanInfo = Introspector.getBeanInfo(ABean.class);

The target bean should follow the JavaBean conventions as described above.



Beans class

This class has static methods to instantiate a bean by it's name.

Some static methods are useful to get/set some global information, probably used in a builder or IDE environment.



Bean Persistence

To support bean persistence, our beans must implement either java.io.Serializable interface, or the java.io.Externalizable interface.

Generally, transient or static fields cannot be serialized

When using the interface Serializable, we can implement these methods with exact the same signatures to customize and control default serialization process:

private void writeObject(java.io.ObjectOutputStream out)
                                          throws IOException;

    private void readObject(java.io.ObjectInputStream in)
                                         throws IOException, ClassNotFoundException;

When using Externalizable we can control the serialization process by implementing it's methods: readExternal and writeExternal .



Persist beans to XML format

The java.beans API provides various classes for that: XMLEncoder, XMLDecoder, PersistenceDelegate, DefaultPersistenceDelegate, Statement, Expression.

The annotation @Transient is used on the method. It indicates that the java.beans.Encoder implementations should ignore this method in encoding process if annotated with @Transient(true). This is relevant to the situation when Introspector constructs a PropertyDescriptor or EventSetDescriptor classes associated with the annotated method.

The annotation java.beans.ConstructorProperties relates the constructor parameters to the properties of beans. This information probably is useful during deserialization of immutable beans. Immutable beans are serialized using getters, they don't have any setters and Java needs to know how to initialize properties using constructor parameters during deserialization time.


Examples


Getting BeanInfo instance via Introspector#getBeanInfo()

By default BeanInfo is constructed through the automatic analysis by using the low-level reflection of the bean methods.

package com.logicbig.example;

import java.beans.*;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

public class IntrospectorExample {

    public static void main (String[] args) throws IntrospectionException, IOException,
                        ClassNotFoundException, InvocationTargetException, IllegalAccessException {

        //introspecting the details of a target bean
        BeanInfo beanInfo = Introspector.getBeanInfo(TheBean.class);

        System.out.println("BeanInfo class: "+beanInfo.getClass());

        //creating an instance of the bean
        TheBean instance = (TheBean) Beans.instantiate(
                            IntrospectorExample.class.getClassLoader(),
                            beanInfo.getBeanDescriptor()
                                    .getBeanClass()
                                    .getName());

        System.out.println("The instance created : " + instance.getClass());

        BeanDescriptor bd = beanInfo.getBeanDescriptor();
        System.out.println("Bean name: " + bd.getName());
        System.out.println("Bean display name: " + bd.getDisplayName());
        System.out.println("Bean class: " + bd.getBeanClass());

        for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
            System.out.println("----------");
            System.out.println("Property Name: " + pd.getName());
            System.out.println("Property Display Name:" + pd.getDisplayName());
            System.out.println("Property Type: " + pd.getPropertyType());

            if (pd.getPropertyType()
                  .isAssignableFrom(String.class)) {
                System.out.println("Property value: " + pd.getReadMethod()
                                                          .invoke(instance));
                pd.getWriteMethod()
                  .invoke(instance, "test-value");
                System.out.println("Property value after setting: " + pd.getReadMethod()
                                                                        .invoke(instance));
            }
        }

        //similarly we can analyse bean's method, event etc..
    }

    public static class TheBean {
        private String someStr;

        public String getSomeStr () {
            return someStr;
        }

        public void setSomeStr (String someStr) {
            this.someStr = someStr;
        }

    }
}

Output

BeanInfo class: class java.beans.GenericBeanInfo
The instance created : class com.logicbig.example.IntrospectorExample$TheBean
Bean name: IntrospectorExample$TheBean
Bean display name: IntrospectorExample$TheBean
Bean class: class com.logicbig.example.IntrospectorExample$TheBean
----------
Property Name: class
Property Display Name:class
Property Type: class java.lang.Class
----------
Property Name: someStr
Property Display Name:someStr
Property Type: class java.lang.String
Property value: null
Property value after setting: test-value

Customizing BeanInfo

By creating a custom implementation of BeanInfo, we can provide additional bean information through various descriptor classes.

package com.logicbig.example;

import java.beans.*;

public class IntrospectorExample2 {
    public static void main (String[] args) throws IntrospectionException {
        //auto discovering ABeanBeanInfo
        BeanInfo beanInfo = Introspector.getBeanInfo(ABean.class);
        System.out.println(beanInfo.getClass());
        System.out.println(beanInfo.getBeanDescriptor()
                                   .getDisplayName());
    }

    public static class ABean {
        private String someStr;

        public String getSomeStr () {
            return someStr;
        }

        public void setSomeStr (String someStr) {
            this.someStr = someStr;
        }

    }

    public static class ABeanBeanInfo extends SimpleBeanInfo {
        @Override
        public BeanDescriptor getBeanDescriptor () {
            BeanDescriptor bd = new BeanDescriptor(ABean.class);
            bd.setDisplayName("A custom Bean display name");
            return bd;
        }
    }
}

Output

class java.beans.GenericBeanInfo
A custom Bean display name

Default PropertyEditor Example

package com.logicbig.example;

import java.awt.*;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;

public class DefaultPropEditorExample {
    public static void main(String[] args) {
        PropertyEditor editor = PropertyEditorManager.findEditor(Font.class);
        //font to text conversion
        editor.setValue(new Font("Dialog", Font.BOLD, 12));
        String asText = editor.getAsText();
        System.out.println(asText);

        //text to font conversion
        PropertyEditor editor2 = PropertyEditorManager.findEditor(Font.class);
        editor2.setAsText("SansSerif ITALIC 14");
        Object value = editor2.getValue();
        System.out.println(value);
    }
}

Output

Dialog BOLD 12
java.awt.Font[family=SansSerif,name=SansSerif,style=italic,size=14]

Creating a custom PropertyEditor

package com.logicbig.example;

import java.beans.*;

public class CustomPropEditor {

    public static void main(String[] args) {
        PropertyEditor editor = PropertyEditorManager.findEditor(User.class);
        editor.setAsText("Joe");
        System.out.println(editor.getValue());
    }

    public static class UserBeanInfo extends SimpleBeanInfo {
        private UserEditor userEditor = new UserEditor();

        @Override
        public PropertyDescriptor[] getPropertyDescriptors() {
            try {
                PropertyDescriptor propertyDescriptor
                        = new PropertyDescriptor("name", User.class) {
                    @Override
                    public PropertyEditor createPropertyEditor(Object bean) {
                        return userEditor;
                    }
                };

                return new PropertyDescriptor[]{propertyDescriptor};

            } catch (IntrospectionException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static class UserEditor extends PropertyEditorSupport {

        @Override
        public String getAsText() {
            User user = (User) getValue();
            return user.getName();
        }

        @Override
        public void setAsText(String s) {
            User user = new User();
            user.setName(s);
            setValue(user);
        }
    }

    public static class User {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
}

Output

User{name='Joe'}

Creating a custom PropertyEditor and registering explicitly

package com.logicbig.example;

import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.beans.PropertyEditorSupport;

public class ExplicitPropEditorRegistration {

    public static void main (String[] args) {
        //auto discovering editor
        PropertyEditor editor = PropertyEditorManager.findEditor(User.class);
        System.out.println("Auto discovered: "+editor.getClass().getSimpleName());

        //auto explicitly registering a specific editor
         PropertyEditorManager.registerEditor(User.class, UserEditor2.class);
        editor = PropertyEditorManager.findEditor(User.class);
        System.out.println("Explicitly registered: "+editor.getClass().getSimpleName());

        editor.setAsText("Joe");
        System.out.println(editor.getValue());
    }

    public static class UserEditor extends PropertyEditorSupport {

        @Override
        public String getAsText () {
            User user = (User) getValue();
            return user.getName();
        }

        @Override
        public void setAsText (String s) {
            User user = new User();
            user.setName(s);
            setValue(user);
        }
    }

    public static class UserEditor2 extends PropertyEditorSupport {

        @Override
        public String getAsText () {
            User user = (User) getValue();
            return user.getName();
        }

        @Override
        public void setAsText (String s) {
            User user = new User();
            user.setName(s);
            setValue(user);
        }
    }

    public static class User {
        private String name;

        public String getName () {
            return name;
        }

        public void setName (String name) {
            this.name = name;
        }

        @Override
        public String toString () {
            return "User{" +
                                "name='" + name + '\'' +
                                '}';
        }
    }
}

Output

Auto discovered: UserEditor
Explicitly registered: UserEditor2
User{name='Joe'}

Example Project

Dependencies and Technologies Used:

  • JDK 16 Version Compatibility: 1.1 - 16 Version List
    ×

    Version compatibilities of JDK with this example:

    • 1.1
    • 1.2
    • 1.3
    • 1.4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Versions in green have been tested.

JavaBeans components quick examples Select All Download
  • java-beans-api
    • src
      • com
        • logicbig
          • example
            • IntrospectorExample.java

    See Also