Los Java Beans -en castellano granos de café java- no son otra cosa que un modelo de componentes para Java. Se estandariza la forma de definir las propiedades (métodos get y set), cómo deben notificarse cambios en los estados del componente (propiedades ligadas, constraints) y cómo acceder a colecciones de elementos (propiedades índice).
Estas notas están sacadas del Tutorial de Java sobre java beans y he extraido aquello que se refiere sólamente al código fuente, dejando de lado los aspectos de diseño y visuales.
Un java bean sencillo:
public class MyBean { /** Creates a new instance of MyBean */ public MyBean() { } /** * Holds value of property yourName. */ private String yourName; /** * Getter for property yourName. * @return Value of property yourName. */ public String getYourName() { return this.yourName; } /** * Setter for property yourName. * @param yourName New value of property yourName. */ public void setYourName(String yourName) { this.yourName = yourName; } }
Las propiedades exponen dos métodos públicos: getNombrePropiedad y setNombrePropiedad. El “set” siempre retorna void, el get siempre retorna el tipo de la propiedad.
Cuando una propiedad debe exponer una colección de elementos (el acceso a una lista de valores), los métodos a programar son cuatro:
/** * Holds value of property lines. */ private String[] lines; /** * Indexed getter for property lines. * @param index Index of the property. * @return Value of the property at index. */ public String getLines(int index) { return this.lines[index]; } /** * Getter for property lines. * @return Value of property lines. */ public String[] getLines() { return this.lines; } /** * Indexed setter for property lines. * @param index Index of the property. * @param lines New value of the property at index. */ public void setLines(int index, String lines) { this.lines[index] = lines; } /** * Setter for property lines. * @param lines New value of property lines. */ public void setLines(String[] lines) { this.lines = lines; }
Lo que haremos en este caso es utilizar un objeto PropertyChangeListener para que cada vez que una de las propiedades cambie, se mande un mensaje a aquellos objetos que se hayan registrado como “listeners 1)” de nuestro bean.
import java.awt.Graphics; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.Serializable; import javax.swing.JComponent; /** * Bean with bound properties. */ public class MyBean extends JComponent implements Serializable { private String title; private String[] lines = new String[10]; private final PropertyChangeSupport pcs = new PropertyChangeSupport( this ); public String getTitle() { return this.title; } public void setTitle( String title ) { String old = this.title; this.title = title; this.pcs.firePropertyChange( "title", old, title ); } public String[] getLines() { return this.lines.clone(); } public String getLines( int index ) { return this.lines[index]; } public void setLines( String[] lines ) { String[] old = this.lines; this.lines = lines; this.pcs.firePropertyChange( "lines", old, lines ); } public void setLines( int index, String line ) { String old = this.lines[index]; this.lines[index] = line; this.pcs.fireIndexedPropertyChange( "lines", index, old, lines ); } public void addPropertyChangeListener( PropertyChangeListener listener ) { this.pcs.addPropertyChangeListener( listener ); } public void removePropertyChangeListener( PropertyChangeListener listener ) { this.pcs.removePropertyChangeListener( listener ); } }
Existe también la posibilidad de que los listeners puedan “vetar” un cambio, que son lo que se llama “constrained properties” 2).
import java.io.Serializable; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.beans.PropertyVetoException; import java.beans.VetoableChangeListener; import java.beans.VetoableChangeSupport; import java.awt.Graphics; import javax.swing.JComponent; /** * Bean with constrained properties. */ public class MyBean extends JComponent implements Serializable { private String title; private String[] lines = new String[10]; private final PropertyChangeSupport pcs = new PropertyChangeSupport( this ); private final VetoableChangeSupport vcs = new VetoableChangeSupport( this ); public String getTitle() { return this.title; } /** * This method was modified to throw the PropertyVetoException * if some vetoable listeners reject the new title value */ public void setTitle( String title ) throws PropertyVetoException { String old = this.title; this.vcs.fireVetoableChange( "title", old, title ); this.title = title; this.pcs.firePropertyChange( "title", old, title ); } public String[] getLines() { return this.lines.clone(); } public String getLines( int index ) { return this.lines[index]; } /** * This method throws the PropertyVetoException * if some vetoable listeners reject the new lines value */ public void setLines( String[] lines ) throws PropertyVetoException { String[] old = this.lines; this.vcs.fireVetoableChange( "lines", old, lines ); this.lines = lines; this.pcs.firePropertyChange( "lines", old, lines ); } public void setLines( int index, String line ) throws PropertyVetoException { String old = this.lines[index]; this.vcs.fireVetoableChange( "lines", old, line ); this.lines[index] = line; this.pcs.fireIndexedPropertyChange( "lines", index, old, line ); } public void addPropertyChangeListener( PropertyChangeListener listener ) { this.pcs.addPropertyChangeListener( listener ); } public void removePropertyChangeListener( PropertyChangeListener listener ) { this.pcs.removePropertyChangeListener( listener ); } /** * Registration of the VetoableChangeListener */ public void addVetoableChangeListener( VetoableChangeListener listener ) { this.vcs.addVetoableChangeListener( listener ); } public void removeVetoableChangeListener( VetoableChangeListener listener ) { this.vcs.removeVetoableChangeListener( listener ); } protected void paintComponent( Graphics g ) { g.setColor( getForeground() ); int height = g.getFontMetrics().getHeight(); paintString( g, this.title, height ); if ( this.lines != null ) { int step = height; for ( String line : this.lines ) paintString( g, line, height += step ); } } private void paintString( Graphics g, String str, int height ) { if ( str != null ) g.drawString( str, 0, height ); } }
Hay dos modos de que un java bean pueda alcanzar la persistencia: serialización y externalización.
La serialización se consigue implentando el interfaz Serializable, que podemos entender como un mero “marcador” que indica que nuestro componente es puede serializarse y por lo tanto ser almacenado en disco –o transferido por la red–.
Si la clase padre ya implementa Serializable, entonces la nuestra es también serializable.
Si queremos que una clase o método no se guarde en el proceso de serialización, usaremos el marcador “transient”.
Podemos hacer que la serialización no funcione por defecto implementando estos dos métodos en nuestra clase:
private void writeObject( java.io.ObjectOutputStream out) throws IOException; private void readObject( java.io.ObjectInputStream out) throws IOException, ClassNotFoundException;
La externalización exige de un mayor trabajo por nuestra parte, y está pensada para que el contenido del java bean se escriba en disco.
Se hace básicamente heredando el interfaz Externalizable e implementando a continuación los métodos readExternal y writeExternal.
La clase que implemente Externalizable tiene que tener un constructor sin argumentos.
La clase XMLEncoder permite hacer esto. Ahí van dos ejemplos:
XMLEncoder e = new XMLEncoder( new BufferedOutputStream( new FileOutputStream("Test.xml"))); e.writeObject(object); e.close()
XMLDecoder decoder = new XMLDecoder( new BufferedInputStream( new FileInputStream( "Test.xml" ) ) ); Object object = decoder.readObject(); decoder.close();