Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |
|||||||
Niveau : | Intermédiaire |
Les JavaBeans sont des composants réutilisables introduits par le JDK 1.1. De nombreuses fonctionnalités ont ensuite été ajoutées pour développer des caractéristiques de ces composants. Les JavaBeans sont couramment appelés beans tout simplement.
Les beans sont prévus pour pouvoir interagir avec d'autres beans au point de pouvoir développer une application simplement en assemblant des beans avec un outil graphique dédié. Sun fournit gratuitement un tel outil : le B.D.K. (Bean Development Kit).
Ce chapitre contient plusieurs sections :
Des composants réutilisables sont des objets autonomes qui doivent pouvoir être facilement assemblés entres eux pour créer un programme.
Microsoft propose la technologie ActiveX pour définir des composants mais celle-ci est spécifiquement destinée aux plates-formes Windows.
Les Java beans proposés par Sun reposent bien sûr sur Java et de fait en possèdent toutes les caractéristiques : indépendance de la plate-forme, taille réduite du composant, ...
La technologie JavaBeans propose de simplifier et faciliter la création et l'utilisation de composants.
Les JavaBeans possèdent plusieurs caractéristiques :
Ainsi, les beans sont des classes Java qui doivent respecter un certain nombre de règles :
Le type de composants le plus adapté est le composant visuel. D'ailleurs, les composants des classes A.W.T. et Swing pour la création d'interfaces graphiques sont tous des beans. Mais les beans peuvent aussi être des composants non visuels pour prendre en charge les traitements.
Les propriétés contiennent des données qui gèrent l'état du composant : elles peuvent être de type primitif ou être un objet.
Il existe quatre types de propriétés :
Les propriétés sont des variables d'instance du bean qui possèdent des méthodes particulières pour lire et modifier leur valeur. La normalisation de ces méthodes permet à des outils de déterminer de façon dynamique quelles sont les propriétés du bean. L'accès à ces propriétés doit se faire grâce à ces méthodes. Ainsi la variable qui stocke la valeur de la propriété ne doit pas être déclarée public mais les méthodes d'accès à cette variable doivent bien sûr l'être.
Le nom de la méthode de lecture d'une propriété doit obligatoirement commencer par "get" suivi par le nom de la propriété dont la première lettre doit être une majuscule. Une telle méthode est souvent appelée "getter" ou "accesseur" de la propriété. La valeur retournée par cette méthode doit être du type de la propriété.
Exemple ( code Java 1.1 ) : |
private int longueur;
public int getLongueur () {
return longueur;
}
Pour les propriétés booléennes, une autre convention peut être utilisée : la méthode peut commencer par «is» au lieu de « get ». Dans ce cas, la valeur de retour est obligatoirement de type boolean.
Le nom de la méthode permettant la modification d'une propriété doit obligatoirement commencer par « set » suivi par le nom de la propriété dont la première lettre doit être une majuscule. Une telle méthode est souvent appelée « setter ». Elle ne retourne aucune valeur et doit avoir en paramètre une variable du type de la propriété qui contiendra sa nouvelle valeur. Elle devra assurer la mise à jour de la valeur de la propriété en effectuant éventuellement des contrôles et/ou des traitements (par exemple le rafraîchissement pour un bean visuel dont la propriété affecte l'affichage).
Exemple ( code Java 1.1 ) : |
private int longueur ;
public void setLongueur (int longueur) {
this.longueur = longueur;
}
Une propriété peut n'avoir qu'un getter et pas de setter : dans ce cas, la propriété n'est utilisable qu'en lecture seule.
Le nom de la variable d'instance qui contient la valeur de la propriété n'est pas obligatoirement le même que le nom de la propriété
Il est préférable d'assurer une gestion des accès concurrents dans ses méthodes de lecture et de mise à jour des propriétés par exemple en déclarant ces méthodes synchronized.
Les méthodes du beans peuvent directement manipuler en lecture et écriture la variable d'instance qui stocke la valeur de la propriété, mais il est préférable d'utiliser le getter et le setter.
Ce sont des propriétés qui possèdent plusieurs valeurs stockées dans un tableau.
Pour ces propriétés, il faut aussi définir des méthodes « get » et « set » dont il convient d'ajouter un paramètre de type int représentant l'index de l'élément du tableau.
Exemple ( code Java 1.1 ) : |
private float[] notes = new float[5];
public float getNotes (int i ) {
return notes[i];
}
public void setNotes (int i, float notes) {
this.notes[i] = notes;
}
Il est aussi possible de définir des méthodes « get » et « set » permettant de lire et de mettre à jour tout le tableau.
Exemple ( code Java 1.1 ) : |
private float[] notes = new float[5];
public float[] getNotes () {
return notes;
}
public void setNotes (float[] notes) {
this.notes = notes;
}
Il est possible d'informer d'autres composants du changement de la valeur d'une propriété d'un bean. Les JavaBeans peuvent mettre en place un mécanisme qui permet pour une propriété d'enregistrer des composants qui seront informés du changement de la valeur de la propriété.
Ce mécanisme peut être mis en place grâce à un objet de la classe PropertyChangeSupport qui permet de simplifier la gestion de la liste des écouteurs et de les informer des changements de valeur d'une propriété. Cette classe définit les méthodes addPropertyChangeListener() pour enregistrer un composant désirant être informé du changement de la valeur de la propriété et removePropertyChangeListener() pour supprimer un composant de la liste.
La méthode firePropertyChange() permet d'informer tous les composants enregistrés du changement de la valeur de la propriété.
Le plus simple est que le bean hérite de la classe PropertyChangeSupport si possible car les méthodes addPropertyChangeListener() et removePropertyChangeListener() seront directement héritées.
Si ce n'est pas possible, il est obligatoire de définir les méthodes addPropertyChangeListener() et removePropertyChangeListener() dans le bean qui appelleront les méthodes correspondantes de l'objet PropertyChangeSupport.
Exemple ( code Java 1.1 ) : |
import java.io.Serializable;
import java.beans.*;
public class MonBean03 implements Serializable {
protected int valeur;
PropertyChangeSupport changeSupport;
public MonBean03(){
valeur = 0;
changeSupport = new PropertyChangeSupport(this);
}
public synchronized void setValeur(int val) {
int oldValeur = valeur;
valeur = val;
changeSupport.firePropertyChange("valeur",oldValeur,valeur);
}
public synchronized int getValeur() {
return valeur;
}
public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(listener);
}
public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(listener);
}
}
Les composants qui désirent être enregistrés doivent obligatoirement implémenter l'interface PropertyChangeListener et définir la méthode propertyChange() déclarée par cette interface.
La méthode propertyChange() reçoit en paramètre un objet de type PropertyChangeEvent qui représente l'événement. Les méthodes propertyChange() de tous les objets enregistrés sont appelées. La méthode propertyChange() reçoit en paramètre un objet de type PropertyChangeEvent qui contient plusieurs informations :
Pour les traitements, il est souvent nécessaire d'utiliser un cast pour transmettre ou utiliser les objets qui représentent l'ancienne et la nouvelle valeur.
Méthode | Rôle |
public Object getSource() | retourne l'objet source |
public Object getNewValue() | retourne la nouvelle valeur de la propriété |
public Object getOldValue() | retourne l'ancienne valeur de la propriété |
public String getPropertyName | retourne le nom de la propriété modifiée |
Exemple ( code Java 1.1 ) : un programme qui créé le bean et lui associe un écouteur |
import java.beans.*;
import java.util.*;
public class TestMonBean03 {
public static void main(String[] args) {
new TestMonBean03();
}
public TestMonBean03() {
MonBean03 monBean = new MonBean03();
monBean.addPropertyChangeListener( new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
System.out.println("propertyChange : valeur = "+ event.getNewValue());
}
} );
System.out.println("valeur = " + monBean.getValeur());
monBean.setValeur(10);
System.out.println("valeur = " + monBean.getValeur());
}
}
Résultat : |
C:\tutorial\sources exemples>java TestMonBean03
valeur = 0
propertyChange : valeur = 10
valeur = 10
Pour supprimer un écouteur de la liste du bean, il suffit d'appeler la méthode removePropertyChangeListener() en lui passant en paramètre une référence sur l'écouteur.
Ces propriétés permettent à un ou plusieurs composants de mettre un veto sur la modification de la valeur de la propriété.
Comme pour les propriétés liées, le bean doit gérer une liste de composants « écouteurs » qui souhaitent être informés d'un changement possible de la valeur de la propriété. Si un composant désire s'opposer à ce changement de valeur, il lève une exception pour en informer le bean.
Les écouteurs doivent implémenter l'interface VetoableChangeListener qui définit la méthode vetoableChange().
Avant le changement de la valeur, le bean appelle cette méthode vetoableChange() de tous les écouteurs enregistrés. Elle possède en paramètre un objet de type PropertyChangeEvent qui contient : le bean, le nom de la propriété, l'ancienne et la nouvelle valeur.
Si un écouteur veut s'opposer à la mise à jour de la valeur, il lève une exception de type java.beans.PropertyVetoException. Dans ce cas, le bean ne change pas la valeur de la propriété : ces traitements sont à la charge du programmeur avec notamment la gestion de la capture et du traitement de l'exception dans un bloc try/catch.
La classe VetoableChangeSupport permet de simplifier la gestion de la liste des écouteurs et de les informer du futur changement de valeur d'une propriété. Son utilisation est similaire à celle de la classe PropertyChangeSupport.
Pour ces propriétés, pour que les traitements soient complets il faut implémenter le code pour gérer et traiter les écouteurs qui souhaitent connaître les changements de valeur effectifs de la propriété (voir les propriétés liées).
Exemple ( code Java 1.1 ) : |
import java.io.Serializable;
import java.beans.*;
public class MonBean04 implements Serializable {
protected int oldValeur;
protected int valeur;
PropertyChangeSupport changeSupport;
VetoableChangeSupport vetoableSupport;
public MonBean04(){
valeur = 0;
oldValeur = 0;
changeSupport = new PropertyChangeSupport(this);
vetoableSupport = new VetoableChangeSupport(this);
}
public synchronized void setValeur(int val) {
oldValeur = valeur;
valeur = val;
try {
vetoableSupport.fireVetoableChange("valeur",new Integer(oldValeur),
new Integer(valeur));
} catch(PropertyVetoException e) {
System.out.println("MonBean, un veto est emis : "+e.getMessage());
valeur = oldValeur;
}
if ( valeur != oldValeur ) {
changeSupport.firePropertyChange("valeur",oldValeur,valeur);
}
}
public synchronized int getValeur() {
return valeur;
}
public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(listener);
}
public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(listener);
}
public synchronized void addVetoableChangeListener(VetoableChangeListener listener) {
vetoableSupport.addVetoableChangeListener(listener);
}
public synchronized void removeVetoableChangeListener(VetoableChangeListener listener) {
vetoableSupport.removeVetoableChangeListener(listener);
}
}
Exemple ( code Java 1.1 ) : un programme qui teste le bean. Il émet un veto si la nouvelle valeur de la propriété est supérieure à 100. |
import java.beans.*;
import java.util.*;
public class TestMonBean04 {
public static void main(String[] args) {
new TestMonBean04();
}
public TestMonBean04() {
MonBean04 monBean = new MonBean04();
monBean.addPropertyChangeListener( new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
System.out.println("propertyChange : valeur = "+ event.getNewValue());
}
} );
monBean.addVetoableChangeListener( new VetoableChangeListener() {
public void vetoableChange(PropertyChangeEvent event) throws PropertyVetoException {
System.out.println("vetoableChange : valeur = " + event.getNewValue());
if( ((Integer)event.getNewValue()).intValue() > 100 )
throw new PropertyVetoException("valeur superieure a 100",event);
}
} );
System.out.println("valeur = " + monBean.getValeur());
monBean.setValeur(10);
System.out.println("valeur = " + monBean.getValeur());
monBean.setValeur(200);
System.out.println("valeur = " + monBean.getValeur());
}
}
Exemple ( code Java 1.1 ) : |
C:\tutorial\sources exemples>java TestMonBean04
valeur = 0
vetoableChange : valeur = 10
propertyChange : valeur = 10
valeur = 10
vetoableChange : valeur = 200
vetoableChange : valeur = 10
MonBean, un veto est emis : valeur superieure a 100
valeur = 10
Toutes les méthodes publiques sont visibles de l'extérieur et peuvent donc être appelées.
Pour dialoguer, Les beans utilisent les événements définis dans le modèle par délégation introduit par le J.D.K. 1.1. Par respect de ce modèle, le bean est la source et les autres composants qui souhaitent être informés sont nommés « Listeners » ou « écouteurs » et doivent s'enregistrer auprès du bean qui maintient la liste des composants enregistrés.
Il est nécessaire de définir les méthodes qui vont permettre de gérer la liste des écouteurs désirant recevoir l'événement. Il faut définir deux méthodes :
L'objet de type XXXListener doit obligatoirement implémenter l'interface java.util.EventListener et son nom doit terminer par « Listener ».
Les événements peuvent être mono ou multi écouteurs.
Pour les événements mono écouteurs, la méthode addXXXListener() doit indiquer dans sa signature qu'elle est susceptible de lever l'exception java.util.TooManyListenersException si un écouteur tente de s'enregistrer et qu'il y en a déjà un présent.
L'introspection est un mécanisme qui permet de déterminer de façon dynamique les caractéristiques d'une classe et donc d'un bean. Les caractéristiques les plus importantes sont les propriétés, les méthodes et les événements. Le principe de l'introspection permet à Sun d'éviter de rajouter des éléments au langage pour définir ces caractéristiques.
L'API JavaBean définit la classe java.beans.Introspector qui facilite et standardise la recherche des propriétés, méthodes et événements du bean. Cette classe possède des méthodes pour analyser le bean et retourner un objet de type BeanInfo contenant les informations trouvées.
La classe Introspector utilise deux techniques pour retrouver ces informations :
Il est donc possible de définir un objet BeanInfo qui sera directement utilisé par la classe Introspector. Cette définition est utile si le bean ne respecte pas certains modèles (design patterns) ou si certaines entités héritées ne doivent pas être utilisables. Dans ce cas, le nom de cette classe doit obligatoirement respecter le modèle XXXBeanInfo ou XXX est le nom du bean correspondant. La classe Introspector recherche une classe respectant ce modèle.
Si une classe BeanInfo est définie pour un bean, une classe qui hérite du bean n'est pas obligée d'en définir une. Dans ce cas, la classe Introspector utilise les informations du BeanInfo de la classe mère et ajoute les informations retournées par l'API Reflection sur le bean.
Sans classe BeanInfo associée au bean, les méthodes de la classe Introspector utilisent les techniques d'introspection pour analyser le bean.
La classe Introspector utilise l'API reflection pour déterminer les informations sur le bean et utilise en même temps un ensemble de modèles sur chacune des entités propriétés, méthodes et événements.
Pour déterminer les propriétés, la classe Introspector recherche les méthodes getXxx(), setXxx() et isXxx() où Xxx représente le nom de la propriété dont la première lettre est en majuscule. La première lettre du nom de la propriété est remise en minuscule sauf si les deux premières lettres de la propriété sont en majuscules.
Pour déterminer les méthodes, la classe Introspector va rechercher toutes les méthodes publiques.
Pour déterminer les événements, la classe Introspector recherche les méthodes addXxxListener() et removeXxxListener(). Si les deux sont présentes, elle en déduit que l'événement xxx est défini dans le bean. Comme pour les propriétés, la première lettre du nom de l'événement est mise en minuscule.
La classe BeanInfo contient des informations sur un bean et possède plusieurs méthodes pour les obtenir.
La méthode getBeanInfo() prend en paramètre un objet de type Class qui représente la classe du bean et elle renvoie des informations sur la classe et toutes ses classes mères.
Une version surchargée de la méthode accepte deux objets de type Class : le premier représente le bean et le deuxième représente une classe appartenant à la hiérarchie du bean. Dans ce cas, la recherche d'informations s'arrêtera juste avant d'arriver à la classe précisée en deuxième argument.
Exemple : obtenir des informations sur le bean uniquement (sans informations sur ses super-classes)
Exemple ( code Java 1.1 ) : |
Class monBeanClasse = Class.forName("monBean");
BeanInfo bi = Introspector.getBeanInfo(monBeanClasse, monBeanClasse.getSuperclass());
La méthode getBeanDescriptor() permet d'obtenir des informations générales sur le bean en renvoyant un objet de type BeanDescriptor()
La méthode getPropertyDescriptors() permet d'obtenir un tableau d'objets de type PropertyDescriptor qui contiennent les caractéristiques d'une propriété. Plusieurs méthodes permettent d'obtenir ces informations.
Exemple ( code Java 1.1 ) : |
PropertyDescriptor[] propertyDescriptor = bi.getPropertyDescriptors();
for (int i=0; i<propertyDescriptor.length; i++) {
System.out.println(" Nom propriete : " +
propertyDescriptor[i].getName());
System.out.println(" Type propriete : "
+ propertyDescriptor[i].getPropertyType());
System.out.println(" Getter propriete : "
+ propertyDescriptor[i].getReadMethod());
System.out.println(" Setter propriete : "
+ propertyDescriptor[i].getWriteMethod());
}
La méthode getMethodDescriptors() permet d'obtenir un tableau d'objets de type MethodDescriptor. Cette classe fournit plusieurs méthodes pour extraire les informations des objets contenus dans le tableau.
Exemple ( code Java 1.1 ) : |
MethodDescriptor[] methodDescriptor = bi.getMethodDescriptors();
for (int i=0; i < methodDescriptor.length; i++) {
System.out.println(" Methode : "+methodDescriptor[i].getName());
}
La méthode getEventSetDescriptors() permet d'obtenir un tableau d'objets de type EventSetDescriptor qui contient les caractéristiques d'un événement. Plusieurs méthodes permettent d'obtenir ces informations.
Exemple ( code Java 1.1 ) : |
EventSetDescriptor[] unEventSetDescriptor = bi.getEventSetDescriptors();
for (int i = 0; i < unEventSetDescriptor.length; i++) {
System.out.println(" Nom evt : "
+ unEventSetDescriptor[i].getName());
System.out.println(" Methode add evt : " +
unEventSetDescriptor[i].getAddListenerMethod());
System.out.println(" Methode remove evt : " +
unEventSetDescriptor[i].getRemoveListenerMethod());
methodDescriptor = unEventSetDescriptor[i].getListenerMethodDescriptors();
for (int j = 0; j < methodDescriptor.length; j++) {
System.out.println(" Event Type: " + methodDescriptor[j].getName());
}
}
Exemple ( code Java 1.1 ) : |
import java.io.*;
import java.beans.*;
import java.lang.reflect.*;
public class BeanIntrospection {
static String nomBean;
public static void main(String args[]) throws Exception {
nomBean = args[0];
new BeanIntrospection();
}
public BeanIntrospection() throws Exception {
Class monBeanClasse = Class.forName(nomBean);
MethodDescriptor[] methodDescriptor;
BeanInfo bi = Introspector.getBeanInfo(monBeanClasse, monBeanClasse.getSuperclass());
BeanDescriptor unBeanDescriptor = bi.getBeanDescriptor();
System.out.println("Nom du bean : " + unBeanDescriptor.getName());
System.out.println("Classe du bean : " + unBeanDescriptor.getBeanClass());
System.out.println("");
PropertyDescriptor[] propertyDescriptor = bi.getPropertyDescriptors();
for (int i=0; i<propertyDescriptor.length; i++) {
System.out.println(" Nom propriete : " +
propertyDescriptor[i].getName());
System.out.println(" Type propriete : "
+ propertyDescriptor[i].getPropertyType());
System.out.println(" Getter propriete : "
+ propertyDescriptor[i].getReadMethod());
System.out.println(" Setter propriete : "
+ propertyDescriptor[i].getWriteMethod());
}
System.out.println("");
methodDescriptor = bi.getMethodDescriptors();
for (int i=0; i < methodDescriptor.length; i++) {
System.out.println(" Methode : "+methodDescriptor[i].getName());
}
System.out.println("");
EventSetDescriptor[] unEventSetDescriptor = bi.getEventSetDescriptors();
for (int i = 0; i < unEventSetDescriptor.length; i++) {
System.out.println(" Nom evt : "
+ unEventSetDescriptor[i].getName());
System.out.println(" Methode add evt : " +
unEventSetDescriptor[i].getAddListenerMethod());
System.out.println(" Methode remove evt : " +
unEventSetDescriptor[i].getRemoveListenerMethod());
methodDescriptor = unEventSetDescriptor[i].getListenerMethodDescriptors();
for (int j = 0; j < methodDescriptor.length; j++) {
System.out.println(" Event Type: " + methodDescriptor[j].getName());
}
}
System.out.println("");
}
}
Il est possible de développer un éditeur de propriétés spécifique pour permettre de personnaliser la modification des paramètres du bean.
|
La suite de cette section sera développée dans une version future de ce document
|
Les propriétés du bean doivent pouvoir être sauvegardées pour être restituées ultérieurement. Le mécanisme utilisé est la sérialisation. Pour permettre d'utiliser ce mécanisme, le bean doit implémenter l'interface Serializable.
Pour diffuser un bean sous forme de jar, il faut définir un fichier manifest.
Ce fichier doit obligatoirement contenir un attribut Name: qui contient le nom complet de la classe (incluant le package) et un attribut Java-Bean: valorisé à True.
Exemple de fichier manifest pour un bean : |
Name: MonBean.class
Java-Bean: True
|
La suite de cette section sera développée dans une version future de ce document
|
Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |