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;
}
}
}
OutputBeanInfo 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;
}
}
}
Outputclass 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);
}
}
OutputDialog 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 + '\'' +
'}';
}
}
}
OutputUser{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 + '\'' +
'}';
}
}
}
OutputAuto discovered: UserEditor Explicitly registered: UserEditor2 User{name='Joe'}
Example ProjectDependencies and Technologies Used: - JDK 21 Version Compatibility: 1.1 - 21
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
- 17
- 18
- 19
- 20
- 21
Versions in green have been tested.
|