Présentation de la technologie JavaBeans

publicité
Masters pro 2ème année de Luminy
Présentation de la technologie JavaBe
Henri Garreta – Octobre 2006
Table des matières
1.
2.
3.
Approche
Constructeurs
Evénements
3.1.
3.2.
3.3.
3.4.
3.5.
4.
Adaptateurs
4.1.
4.2.
5.
Principe
Propriétés indicées
Propriétés liées
Propriétés contraintes
Introspection
Persistance
6.1.
6.2.
6.3.
6.4.
6.5.
7.
8.
Réfexion
Adaptateurs génériques
Propriétés
5.1.
5.2.
5.3.
5.4.
5.5.
6.
Objets événements
Auditeurs d’événements
Sources d’événements
Evénements et threads multiples
Exemple
Flux d’entrée-sortie
Sérialisation des objets
Sérialisation explicite
Objets « externalisables »
Instanciation de beans sérialisés
Archives jar
Quelques références
PolyJavaBeans.doc (état du 02/10/06 à 15:48)
2
3
3
4
4
4
5
5
6
6
7
8
8
9
9
11
12
13
13
13
15
16
16
17
18
1. Approche
Il n’est pas facile de donner une défnition précise et concise d’un bean Java (ou grain – sans doute de café…). On se
contente habituellement du point de départ suivant :
Un bean Java est un composant logiciel réutilisable qui peut être manipulé visuellement dans un outil de
construction de programmes.
Les beans sont souvent des composants visuels (par exemple, tous les objets java.awt.Component, c’est-à-dire tous
les composants AWT et Swing, sont des beans), mais cela n’est pas une obligation ; un bean peut assurer une tâche qui
ne requiert pas une interface avec l’utilisateur, et rester donc invisible.
Ce qui est essentiel est que les beans sont destinés à être assemblés pour constituer des applications. Cela se fait
souvent à l’aide d’outils visuels de construction de programmes, mais ce n’est pas la seule manière, les beans se prêtent
également très bien à être utilisés dans des programmes écrits de manière classique.
La technologie JavaBean est entièrement incluse dans les spécifcations du langage et de la plate-forme Java : d’une
part, l’écriture d’un bean ne demande aucun concept ou élément syntaxique nouveau ; d’autre part, son utilisation ne
1
requiert aucune bibliothèque ni extension particulière. Il n’existe pas de classe
Bean , les beans ne forment pas une
hiérarchie de classes, ni même une API : tout objet conforme à certaines règles peut s’appeler un bean.
Trois notions fondamentales de la technologie JavaBean :
• Règles de dénomination. Un petit nombre de règles d’écriture et de « nommage » des méthodes doivent être
respectées lorsqu’on écrit un bean. Elles permettent, aussi bien à un lecteur humain qu’à un outil de développement, de reconnaître et de manipuler les caractéristiques d’un bean (c.-à-d. ses propriétés – sortes de variables
« très » publiques -, méthodes et événements), sans avoir à en connaître l’implémentation.
•
Réfexion. Elément important de Java, le mécanisme de la réfexion (cf. section 4.1, page 6) permet à un outil de
développement d’inspecter les objets durant l’exécution, de connaître et analyser les noms de leurs membres,
d’accéder aux valeurs des variables et d’appeler les méthodes. La réfexion permet à des beans qui se rencontrent
durant l’exécution de faire presque tout ce qui aurait été possible s’ils avaient été assemblés durant la compilation.
•
Persistance. Il est possible en Java de « geler » un objet existant, avec son état courant, et de le ranger dans un
fchier en vue de le réactiver ultérieurement. Par exemple, cela permet de fxer les propriétés d’un bean durant le
développement, de sorte que son initialisation au moment de l’exécution se réduira au rechargement du bean
sérialisé. De plus, cela offre un mécanisme original pour instancier (i.e. allouer et initialiser) un bean : cloner un
bean sérialisé.
Les principaux éléments de Java que les beans mettent en œuvre sont les suivants :
• Evénements. La communication entre beans (a priori développés indépendamment les uns des autres) est assurée
par le mécanisme des événements. Une manière d’attacher deux beans A et B consiste à enregistrer B comme
« auditeur » (listener) d’événements dont A est la source ; cela établit une voie de communication de A vers B.
•
Propriétés. L’état d’un bean est défni par les valeurs de propriétés. En programmation « à la main », chaque
propriété se manifeste par un couple de méthodes get<Prop> et set<Prop>. Dans un outil de développement, elles
apparaissent sur des feuilles de propriétés qui en rendent graphiques la consultation et la modifcation.
Défnir B comme valeur d’une propriété de A est une autre manière d’attacher deux beans A et B.
•
Sérialisation. Autant que possible, l’état d’un bean peut être sauvegardé (on dit sérialisé) dans un fchier,
totalement ou partiellement, en vue de sa désérialisation ultérieure.
•
Archivage. Un bean se compose généralement de plusieurs classes. L’utilitaire jar permet de réunir celles-ci en un
unique fchier, compressé, utilisable par la machine Java grâce à un « manifeste » qui en décrit le contenu.
1
Il y a bien une classe java.beans.Beans (notez le pluriel), mais elle n’est pas destinée à avoir des instances. C’est
plutôt une bibliothèque, entièrement faite de membres statiques. Le rôle de cette classe, comme des autres éléments des
paquets java.beans et java.beans.beancontext, est d’aider le programmeur dans la conception et l’utilisation
des beans.
Présentation de la technologie JavaBeans
2
2. Constructeurs
Un bean doit avoir un constructeur sans arguments 2.
C’est la seule contrainte stricte que supportent les beans : tout objet qui a un constructeur sans arguments peut légitimement s’appeler bean (mais il ne sera peut-être pas très utile). D’autre part, ce n’est pas une obligation, mais il est
fortement recommandé de permettre aux beans d’être sérialisés. Ainsi, par exemple, voici une « version bean » du
concept de nombre entier :
public class Entier implements java.io.Serializable {
private int valeur = 0;
public int getValeur() {
return valeur;
}
public void setValeur(int valeur) {
this.valeur = valeur;
}
}
Cela semble bizarre, en défnissant un objet pour représenter un nombre, de ne pas écrire un constructeur permettant de
donner la valeur de ce nombre. C’est que le constructeur d’un bean n’est pas un élément très important.
En effet, en règle générale la « vraie » construction d’un bean ne se produit pas au sein de l’application qui utilise le
bean, durant l’exécution, mais auparavant, dans l’outil de développement qui a servi à assembler l’application. Dans cet
outil, un certain éditeur de propriétés aura permis de défnir l’état du bean à partir d’un ensemble de valeurs utiles. A la
fn du développement, le bean, dans l’état ainsi défni et ensemble avec les autres composants de l’application, aura été
« congelé » (sérialisé) dans un fchier.
Ultérieurement, quand l’application sera exécutée et que le bean deviendra nécessaire, il sera simplement « décongelé »
(désérialisé) et il aura tout de suite les valeurs intéressantes défnies durant le développement. Cette désérialisation d’un
bean sérialisé, distincte de la construction ou instanciation habituelle, s’appelle instanciation de bean.
3. Evénements
La technologie JavaBeans utilise, sans ajout ni modifcation, le modèle événementiel de Java, introduit dans le langage
lors de la parution de la version 1.1.
Un événement commence à exister quand un objet S construit une instance E d’une certaine sous-classe de la classe
EventObject et l’envoie à un ou plusieurs objets A i qui ont été préalablement inscrits comme devant être « avertis »
chaque fois qu’une telle chose se produirait. On dit que E est l’événement, S est la source de l’événement et que les A
sont les auditeurs (listener) de l’événement E pour la source S. Au lieu d’envoi on dit plutôt notifcation d’un
événement ; cela consiste dans le fait que la source S appelle une certaine méthode, avec E pour argument, sur chacun
des auditeurs Ai.
i
notife un événement
Source d’événements
Objet
événement
s’enregistre comme auditeur
Auditeur d’événements
Dans les interfaces graphiques ce mécanisme est principalement employé pour notifer la détection des actions de
l’utilisateur et provoquer les réactions appropriées de la part des objets adéquats : S est souvent un composant
graphique et E rend compte d’un événement extérieur (produit dans le monde réel) qui affecte S, comme un clic avec la
2
Ne pas oublier que tel est le cas d’une classe dans laquelle on n’a explicité aucun constructeur.
Présentation de la technologie JavaBeans
3
souris, une frappe au clavier, etc. Dans le cas des beans ce mécanisme est généralisé et devient le principal moyen de
communication entre beans.
Les événements sont regroupés en catégories – éventuellement réduites à un seul élément – d’événements voisins.
Exemples, tirés de awt : MouseEvent (événements liés à la souris), KeyEvent (événements en rapport avec le clavier)
ActionEvent (qui contient un seul événement, signalant l’actionnement d’un bouton), etc.
Dire qu’un objet O peut être auditeur d’une catégorie <C> d’événements c’est dire que O possède toutes les méthodes
par lesquelles les événements de <C> sont notifés. En Java, cela se fait en déclarant que O implémente une certaine
interface, défnie à cet effet. L’interface correspondant à une catégorie <C>Event se nomme <C>Listener.
Exemples : MouseListener, KeyListener, ActionListener, etc.
Les auditeurs d’un événement doivent être enregistrés auprès de la source de tels événements. Cela se fait par un appel
de la méthode add<C>Listener(<C>Listener X), qui incorpore X dans la liste des auditeurs à prévenir lorsque
<C> se produit. Exemples : addMouseListener, addKeyListener, addActionListener, etc.
3.1. Objets événements
Un objet événement est instance d’une classe, spécifque de l’événement en question. Par convention, le nom de cette
classe est de la forme <C>Event. Exemple : PropertyChangeEvent.
Cette classe doit être sous-classe de la classe java.util.EventObject, dont l’unique constructeur se présente :
public EventObject(Object source)
Il en découle qu’un événement comporte toujours la référence de l’objet qui en est la source. Exemple de constructeur
pour l’événement donné en exemple :
public PropertyChangeEvent(Object source,
String propertyName, Object oldValue, Object newValue);
3.2. Auditeurs d’événements
Notifer un événement c’est appeler une méthode, spécifque de l’événement ou d’une catégorie d’événements, avec
pour argument l’événement en question. Par exemple, un bean notife que la valeur d’une de ses propriétés a changé en
appelant la méthode :
public void propertyChange(PropertyChangeEvent evt);
L’ensemble des méthodes de notifcation des événements d’une catégorie donnée sont rassemblées en une interface
(qui, de fait, défnit la catégorie en question). Cette interface doit être sous-interface de l’interface
java.util.
EventListener. Exemple :
public interface PropertyChangeListener extends EventListener {
void propertyChange(PropertyChangeEvent evt);
}
Un auditeur (listener) d’une catégorie d’événements est un objet auquel doivent être notifés les événements en
question lorsqu’ils surviennent.
Deux conditions pour qu’un objet L soit auditeur des évènements d’une catégorie <C>, dont la source est un objet S :
• on doit enregistrer L, auprès de S, comme auditeur des événements <C>. Cela se fait par une expression telle que :
S.add<C>Listener(L);
•
pour cela, l’objet L doit implémenter l’interface <C>Listener.
3.3. Sources d’événements
Pour qu’on objet soit source d’événements d’une certaine catégorie < C> il doit offrir les deux méthodes
public void add<C>Listener(<C>Listener obj);
public void remove<C>Listener(<C>Listener obj);
Inversement, repérer dans une classe les méthodes ayant les noms et les signatures précédentes permet de découvrir les
événements dont les objets de ce type peuvent être la source.
Présentation de la technologie JavaBeans
4
3.4. Evénements et threads multiples
Un auditeur peut recevoir des notifcations d’événements faites par des méthodes appelées dans plusieurs threads
différents. Il faut donc prendre des mesures, pour éviter aussi bien des comportements incohérents que des blocages du
programme (étreinte mortelle). Des principes à observer :
• il est recommandé que les méthodes de notifcation, dans les auditeurs d’événements, soient synchronisées (c’està-dire qualifées synchronized), de manière à ce que les éventuelles réactions à un même événement notifé
depuis plusieurs sources prennent place les unes après les autres ;
•
l’objet source d’un événement ne doit pas être verrouillé lorsqu’il appelle une méthode de notifcation sur un
auditeur de cet événement car la réaction de l’auditeur pourrait être l’appel d’une méthode de l’objet source – c’est
un cas fréquent – et le blocage serait alors probable ;
•
pour notifer un événement, l’objet source doit parcourir la liste des auditeurs ; or, la réaction d’un auditeur peut
être de modifer cette liste (par exemple, suite à l’événement, l’auditeur s’enlève de la liste, ou bien lui ajoute un
nouvel auditeur) ; il y a alors un risque que le parcours devienne incohérent. En programmant l’opération « notifer
un événement à tous les auditeurs » il faut donc prendre soin de parcourir non pas la liste des auditeurs, mais un
clone de cette liste, produit à cette occasion : voyez notifyTemperatureChange dans l’exemple suivant.
3.5. Exemple
Beaucoup des exemples suivants sont extraits du livre de R. Englander (cf. § 8. Références).
Un objet PointChaud possède constamment une température courante. Lorsque celle-ci change, il prévient tous les
objets Thermostat concernés par ce phénomène.
Un objet TempChangeEvent est la notifcation d’un changement de température. Fichier TempChangeEvent.java :
public class TempChangeEvent extends EventObject {
protected double temp;
public TempChangeEvent(Object source, double temp) {
super(source);
this.temp = temp;
}
public double getTemperature() {
return temp;
}
}
Un objet qui prétend pouvoir jouer le rôle d’un thermostat doit implémenter l’interface TempChangeListener. Fichier
TempChangeListener.java :
public interface TempChangeListener extends java.util.EventListener {
void tempChange(TempChangeEvent evt);
}
Un point chaud doit gérer la liste des thermostats qui lui ont été posés. Fichier PointChaud.java :
public class PointChaud {
protected double tempCour = 18;
private Vector tempChangeListeners = new Vector();
public double getTemperatureCourante() {
return tempCour;
}
public void setTemperatureCourante(double temp) {
tempCour = temp;
notifyTemperatureChange();
}
public synchronized void addTempChangeListener(TempChangeListener l) {
if ( ! tempChangeListeners.contains(l))
tempChangeListeners.addElement(l);
}
public synchronized void removeTempChangeListener(TempChangeListener l) {
if (tempChangeListeners.contains(l))
tempChangeListeners.removeElement(l);
}
Présentation de la technologie JavaBeans
5
protected void notifyTemperatureChange() {
TempChangeEvent evt = new TempChangeEvent(this, tempCour);
Vector v;
synchronized(this) {
v = (Vector) tempChangeListeners.clone();
}
Iterator iter = v.iterator();
while (iter.hasNext())
((TempChangeListener) iter.next()).tempChange(evt);
}
On trouve probablement ici d’autres variables et méthodes
(supporter des thermostats n’est sans doute pas la seule raison d’être d’un point chaud…)
}
Un thermostat est toujours créé en le « posant » sur un point chaud préexistant. Fichier Thermostat.java :
public class Thermostat implements TempChangeListener {
private PointChaud pointChaud;
public void appliquerSur(PointChaud pointChaud) {
this.pointChaud = pointChaud;
pointChaud.addTempChangeListener(this);
}
public void tempChange(TempChangeEvent evt) {
réagir à la nouvelle température
qu’on obtient ici par l’expression evt.getTemperature();
}
}
4. Adaptateurs
La situation suivante est un exemple caractéristique des problèmes à résoudre quand on développe un outil pour
assembler les beans durant l’exécution 3 :
Un auditeur d’un certain type d’événements peut recevoir des notifcations de plusieurs sources ; dans de nombreux
cas, la réaction dépend de la source qui a produit l’événement. C’est une sorte de démultiplexage : les notifcations
arrivent par une voie unique – la méthode associée au type d’événement en question – et il faut en traduire chaque
appel par l’appel d’une autre méthode, spécifque de la source de l’événement.
Si on a le droit de recompiler le programme (c.-à-d. si on travaille avec le texte source), la manière classique de régler
cela consiste à écrire du code : une cascade de if, un switch, etc., pour choisir l’appel de la méthode idoine d’après la
valeur de evt.getSource() (evt représente l’événement en question). Mais c’est pénible et peu fable, car l’enregistrement de chaque nouvelle source se traduit par l’écriture de code additionnel. Inconvénient plus important, cette
manière de faire ne convient pas pour des beans déjà compilés qu’on assemble durant l’exécution.
En utilisant le mécanisme de la réfexion pour écrire des adaptateurs on obtient des auditeurs bien plus simples et
commodes à utiliser. Un certain nombre de tels adaptateurs, en réalité un peu plus complexes que celui que nous allons
montrer ici, existent et sont abondamment utilisés par tous les outils de développement graphique d’interfaces.
4.1. Réfexion
Rappelons brièvement les principaux éléments du mécanisme de la réfexion de Java.
Les instances de la classe java.lang.Class représentent les classes utilisées par un programme en cours d’exécution. Il existe trois manières d’obtenir de tels objets (dans les trois exemples suivants, la variable uneClasse obtient
pour valeur la classe Button appartenant au paquetage java.awt) :
• utiliser la désignation statique de la classe :
Class uneClasse = Button.class;
3
Des outils pour assembler des beans vous en rencontrerez deux sortes principales. D’une part, les jouets créés chez
Sun pour montrer que « ça » marche : la BeanBox, obsolète, et son successeur plus brillant, le BeanBuilder. D’autre
part, excusez du peu, tous les IDE (environnement de développement intégré) qui permettent la construction « à la
souris » d’interfaces graphiques : JBuilder de Borland, netBeans de Sun, Eclipse Visual Editor de eclipse.org, etc.
Présentation de la technologie JavaBeans
6
•
demander quelle est sa classe à un objet existant. Exemple (on suppose que unBouton est une instance existant par
ailleurs de la classe java.awt.Button) :
Class uneClasse = unBouton.getClass();
•
enfn, la manière la plus onéreuse, charger une classe à partir de son nom :
Class uneClasse = Class.forName("java.awt.Button");
Ayant une classe, on peut ensuite en obtenir les méthodes, qui seront représentées par des instances de la classe
java.lang.reflect.Method. La méthode getMethod() de la classe Class prend comme arguments une chaîne,
le nom de la méthode, et un tableau de classes, les types des arguments, et renvoie un objet
Method représentant la
méthode correspondante.
Par exemple, uneClasse étant initialisée comme précédemment, le code suivant affecte à la variable
méthode setLabel(String label) de la classe Button :
uneMethode la
Class[] listeArguments = { String.class };
Method uneMethode = uneClasse.getMethod("setLabel", listeArguments);
Enfn, si on a une méthode d’une classe et une instance de cette classe, on peut appeler la première à travers la seconde.
La méthode invoke de la classe Method prend comme arguments un objet, la cible de l’appel, et une liste d’objets, les
arguments effectifs de l’appel. Voici un appel de la méthode affectée ci-dessus à la variable uneMethode :
Button unBouton = new Button("Stop");
...
Object[] listeParametres = { "Oui" };
uneMethode.invoke(unBouton, listeParametres);
Compte tenu des affectations précédentes, la ligne ci-dessus a le même effet que :
unBouton.setLabel("Oui");
4.2. Adaptateurs génériques
A titre d’exemple, voici la classe AdaptaTemp, un adaptateur d’événements TempChange permettant que dans un
auditeur donné, ici un objet Thermostat, un nombre quelconque de sources de l’événement
TempChange soient
respectivement associées à différentes méthodes de réaction à cet événement.
Auditeur
Méthode 1
Méthode 2
Méthode 3
Adaptateur
Source 1
Source 2
Source 3
Classe AdaptaTemp (pour Adaptateur de Température) :
import java.lang.reflect.*;
import java.util.*;
public class AdaptaTemp implements TempChangeListener {
Object cible;
Map dico = new Hashtable();
AdaptaTemp(Object cible) {
this.cible = cible;
}
Présentation de la technologie JavaBeans
7
void enregistrer(PointChaud source, String nomMethode)
throws NoSuchMethodException {
Method methode = cible.getClass()
.getMethod(nomMethode, new Class[] { TempChangeEvent.class });
dico.put(source, methode);
source.addTempChangeListener(this);
}
public void tempChange(TempChangeEvent evt) {
try {
Method methode = (Method) dico.get(evt.getSource());
methode.invoke(cible, new Object[] { evt });
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
Enfn, voici un objet Thermostat qui reçoit les notifcations de changement de température de trois sources différentes et y réagit par trois méthodes différentes :
Fichier Thermostat.java :
public class TriThermostat {
protected PointChaud source1, source2, source3;
protected AdaptaTemp adaptateur;
public TriThermostat(PointChaud s1, PointChaud s2, PointChaud s3) {
source1 = s1;
source2 = s2;
source3 = s3;
adaptateur = new AdaptaTemp(this);
try {
adaptateur.enregistrer(s1, "changementTemp1");
adaptateur.enregistrer(s2, "changementTemp2");
adaptateur.enregistrer(s3, "changementTemp3");
} catch (NoSuchMethodException e) {
e.printStackTrace();
System.exit(-1);
}
}
public void changementTemp1(TempChangeEvent evt) {
...
}
public void changementTemp2(TempChangeEvent evt) {
...
}
public void changementTemp3(TempChangeEvent evt) {
...
}
}
5. Propriétés
5.1. Principe
Les propriétés sont des attributs « particulièrement visibles » des beans.
Une propriété a un nom, <nomPropriété>, un type, <TypePropriété>, et se manifeste à travers deux méthodes :
public void set<nomPropriété>(<TypePropriété> valeur);
public <TypePropriété> get<nomPropriété>();
Lorsque le type de la propriété est boolean, la méthode get peut prendre la forme :
public boolean is<nomPropriété>();
Présentation de la technologie JavaBeans
8
La méthode setPropriété n’est pas obligatoire. On dit qu’une propriété existe dès que l’objet en question possède la
méthode getPropriété. Si la méthode setPropriété n’existe pas on dit qu’il s’agit une propriété « en lecture seulement ».
Exemples :
public void setTemperatureCourante(int valeur);
public int getTemperatureCourante();
public void setEnMarche(boolean ouiNon);
public boolean isEnMarche();
Les propriétés suggèrent l’existence d’une variable d’instance ayant le nom de la propriété et mémorisant la valeur de
cette dernière. Assez souvent, les méthodes set et get sont de la forme :
public void set<nomPropriété>(<TypePropriété> valeur) {
<nomPropriété> = valeur;
...
}
public <TypePropriété> get<nomPropriété>() {
...
return <nomPropriété>;
}
On notera cependant que cela n’est pas du tout obligatoire. Derrière une propriété il peut y avoir une variable d’instance
ayant un tout autre nom, ou même pas de variable du tout. Ce qui fait la propriété n’est pas l’existence d’une variable
associée, mais les méthodes, généralement publiques, getPropriété, setPropriété.
5.2. Propriétés indicées
Les propriétés qui représentent des collections de valeurs de même type
<TypeElementaire> ont des méthodes
supplémentaires. D’une part il y a deux méthodes qui présentent la collection comme un tableau :
public <TypeElementaire>[] get<nomPropriete>();
public void set<nomPropriete>(<TypeElementaire>[] valeur);
D’autre part il y a deux méthodes qui permettent d’atteindre un élément particulier de la collection :
public <TypeElementaire> get<nomPropriete>(int indice);
public void set<nomPropriete>(int indice, <TypeElementaire> valeur);
On peut donc écrire l’accès à la i ème valeur élémentaire d’une propriété indicée de deux manières :
get<nomPropriete>()[i]
get<nomPropriete>(i)
Elles semblent équivalentes, mais on prendra garde au fait que la première forme peut entraîner un travail supplémentaire (la construction d’un tableau rendu comme résultat) si la valeur de la propriété n’est pas mémorisée de
manière interne dans un tableau.
5.3. Propriétés liées
Une propriété liée génère un événement lorsque sa valeur change. D’autres objets peuvent s’enregistrer comme
auditeurs pour un tel événement.
Un événement signifant « une de mes propriétés a changé » est défni comme ceci :
public class java.beans.PropertyChangeEvent extends java.util.EventObject {
PropertyChangeEvent(Object source, String propertyName,
Object oldValue, Object newValue);
public String getPropertyName();
public Object getOldValue();
public Object getNewValue();
public Object getPropagationId();
public void setPropagationId(Object id);
}
Si cet événement est déclenché c’est qu’une propriété a été modifée. getPropertyName renvoie son nom ;
getOldValue et getNewValue son ancienne et sa nouvelle valeur. Notez que les valeurs (
oldValue, newValue)
sont des objets ; dans le cas de types primitifs, il faudra utiliser les « classes d’emballage » Integer, Double, etc.
L’identifcateur de propagation ( propagationId) est réservé pour un usage futur. L’idée est la suivante : la source
initiale de l’événement crée une valeur unique pour cet identifcateur. Chaque objet qui réagit à l’événement en en
émettant un autre met dans le nouvel événement émis la même valeur d’identifcation. Cela doit permettre de repérer
Présentation de la technologie JavaBeans
9
tous les effets du premier événement et en particulier d’identifer la source initiale (la réponse à la question « qui c’est
qui a commencé ? ») d’une éventuelle cascade de réactions.
Un objet qui a des propriétés liées est source d’événements
PropertyChangeEvent ; il doit donc posséder les
méthodes nécessaires à l’enregistrement des auditeurs 4 (ces méthodes sont tout à fait analogues aux
méthodes
addTempChangeListener et removeTempChangeListener de l’exemple de la section 3.5) :
public void addPropertyChangeListener(PropertyChangeListener l);
public void removePropertyChangeListener(PropertyChangeListener l);
L’interface PropertyChangeListener est défnie comme ceci :
public interface PropertyChangeListener extends EventListener {
void propertyChange(PropertyChangeEvent evt);
}
Aide pour les propriétés liées
Etant source d’événements, un objet qui possède une propriété liée doit fournir tout un matériel obligatoire :
• des méthodes pour l’ajout et la suppression d’auditeurs enregistrés,
•
une méthode pour la notifcation des événements aux auditeurs enregistrés, nommée par exemple firePropertyChange (analogue à la méthode notifyTemperatureChange de l’exemple 3.5)
L’écriture de ces méthodes est une corvée, dans laquelle l’emploi d’un objet java.beans.PropertyChangeSupport
se révèle une aide précieuse :
public class java.beans.PropertyChangeSupport implements java.io.Serializable {
public PropertyChangeSupport(Object source);
public synchronized void addPropertyChangeListener(PropertyChangeListener l);
public synchronized void removePropertyChangeListener(PropertyChangeListener l);
public void fire PropertyChange(String propertyName,
Object oldValue, Object newValue);
}
Essentiellement, un objet PropertyChangeListener se charge de la gestion de la liste des auditeurs. Voici comment
l’utiliser :
public class PointChaud {
protected double tempCour = 18;
PropertyChangeSupport aide;
public PointChaud() {
aide = new PropertyChangeSupport(this);
}
public void addPropertyChangeListener(PropertyChangeListener l) {
aide.addPropertyChangeListener(l);
}
public void removePropertyChangeListener(PropertyChangeListener l) {
aide.removePropertyChangeListener(l);
}
public double getTemperatureCourante() {
return tempCour;
}
public void setTemperatureCourante(double temp) {
Double ancienne = new Double(tempCour);
tempCour = temp;
Double nouvelle = new Double(tempCour);
aide.firePropertyChange("temperatureCourante", ancienne, nouvelle);
}
}
Chez un auditeur qui, par exemple, chercherait à déterminer le maximum des températures des sources qui lui sont
connectées, la fonction de notifcation propertyChange pourrait être ainsi écrite :
4
Les outils de développement prennent la question à l’envers : si un objet possède les méthodes
addProperty-
ChangeListener et removePropertyChangeListener alors il a des propriétés liées.
Présentation de la technologie JavaBeans
10
public class Thermometer implements PropertyChangeListener {
double temperatureMax;
...
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName() == "temperatureCourante") {
double v = ((Double) evt.getNewValue()).doubleValue();
if (v > temperatureMax)
temperatureMax = v;
}
}
...
}
5.4. Propriétés contraintes
Une propriété contrainte est une propriété dont le changement requiert le « feu vert » d’autres objets, préalablement
enregistrés comme susceptibles d’opposer leur veto. Si un de ces autres objets s’oppose au changement l’exception
PropertyVetoException est lancée.
Pour cette raison, la méthode set est un peu différente pour une propriété contrainte :
public <TypePropriété> get<nomPropriété>();
public void set<nomPropriété>(<TypePropriété> valeur)
throws java.beans.PropertyVetoException;
Un objet « demande la permission » de changer la valeur d’une propriété contrainte en notifant un événement
PropertyChangeEvent à l’aide de la méthode vetoableChange. L’objet doit donc posséder les deux méthodes
public void addVetoableChangeListener(VetoableChangeListener l);
public void removeVetoableChangeListener(VetoableChangeListener l);
L’interface VetoableChangeListener est défnie comme ceci :
public interface VetoableChangeListener extends EventListener {
void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException;
}
Les propriétés contraintes sont souvent aussi des propriétés liées. On notera que
• l’événement qui exprime la demande d’approbation, vetoableChange(PropertyChangeEvent evt) , doit être
émis avant la modifcation de la valeur de la propriété,
• l’événement qui exprime le changement, propertyChange(PropertyChangeEvent evt) , doit être émis après
la modifcation de la valeur de la propriété.
Exemple
Imaginons un programme pour simuler la régulation de la température dans un local. Il y aura un objet
PointChaud,
avec une propriété contrainte temperatureCourante, qui simule la température dans le local. Des objets
Thermometre (mesure de la température) et Thermostat (pilotage d’appareils de chauffage ou de climatisation, en
fonction de la température) sont enregistrés, auprès de l’objet PointChaud, comme des PropertyChangeListener
pour la propriété temperatureCourante.
Un ou plusieurs objets Chaudiere et Climatiseur existent également, pilotés par un objet Thermostat. Ce dernier
possède une propriété temperatureSouhaitee que l’utilisateur peut modifer à l’aide de boutons « + » et « − ». Il est
facile de voir que cette propriété doit être une propriété contrainte : les appareils de chauffage et de climatisation
doivent pouvoir opposer leur veto à la demande d’une température si haute ou si basse qu’elle leur serait inatteignable.
Voici à quoi ressemblerait la méthode setTemperatureSouhaitee dans ces conditions :
public class Thermostat extends ... implements ... {
protected double tempSouhaitee = 18.0;
protected VetoableChangeSupport aideVeto;
protected PropertyChangeSupport aideChang;
...
public Thermostat() {
aideVeto = new VetoableChangeSupport();
aideChang = new PropertyChangeSupport();
...
}
Présentation de la technologie JavaBeans
11
void setTemperatureSouhaitee(double temp) {
Double ancienne = new Double(tempSouhaitee);
Double nouvelle = new Double(temp);
try {
aideVeto.fireVetoableChange("tempSouhaitee", ancienne, nouvelle);
}
catch (PropertyVetoException exc) {
return;
// zut, quelqu’un s’est opposé à ce changement
}
// tout va bien, personne ne s’oppose au changement
tempeSouhaitee = temp;
aideChang.firePropertyChange("tempeSouhaitee", ancienne, nouvelle);
}
...
}
5.5. Introspection
Un outil 5 devant manipuler un bean obtient l’information sur les propriétés, les méthodes et les événements de ce
dernier à travers un objet, instance de la classe
java.beans.Introspector, qui effectue un travail « d’introspection » à deux niveaux :
• d’abord, une classe nommée
, implémentant l’interface java.beans.BeanInfo, est
<nomDuBean>BeanInfo
recherchée (pour commencer dans le paquet du bean en question, puis dans d’autres paquets spécifés au niveau de
l’introspecteur) ; si une telle classe existe, une instance en est créée et utilisée pour obtenir les informations sur le
bean ;
•
sinon, le mécanisme de la réfexion de Java est utilisé, pour obtenir la liste des méthodes du bean, sur laquelle les
modèles de conception précédemment expliqués sont exploités pour en déduire les propriétés et événements. Par
exemple, de l’existence d’une méthode <type> get<prop>() on déduit l’existence d’une propriété nommée
6
<prop> , de type <type> , considérée « en lecture et écriture » ou « en lecture seule » selon qu’il existe ou non une
méthode set<prop>(<type> p) .
La deuxième solution n’est pas la meilleure : elle oblige à être très strict dans le nommage des membres des classes, et
a tendance à ne pas exhiber toutes les propriétés qu’on voudrait (le mécanisme manque de souplesse), ou bien à exhiber
des propriétés sans intérêt (exemple : des monceaux de propriétés héritées). Or, la première solution est complexe,
l’interface BeanInfo est loin d’être simple.
Heureusement, on peut adopter une conduite intermédiaire, en défnissant une classe <nomDuBean>BeanInfo comme
sous-classe de la classe java.beans.SimpleBeanInfo, une implémentation de BeanInfo entièrement faite de
méthodes qui rendent comme résultats ceux que fournit la réfexion.
Parmi les principales méthodes de l’interface BeanInfo :
• getPropertyDescriptors() renvoie un tableau dont chaque élément décrit une propriété. Fondamentalement,
décrire une propriété c’est donner les noms des méthodes getPropriété et setPropriété correspondantes, qui doivent
exister dans le bean concerné.
•
getEventSetDescriptors() renvoie un tableau dont chaque élément décrit une sorte d’événements. Essentiel-
lement, cela consiste à donner son nom, la classe de l’auditeur adéquat et le nom de la méthode qui, dans
l’auditeur, doit être appelée lorsque l’événement se produit.
•
getMethodDescriptors() renvoie un tableau dont chaque élément renvoie une méthode.
Pour plus de détails se reporter à la documentation des paquetages java.beans et java.beans.beancontext.
5
Parmi ces outils, outre les vrais environnements de développement comme JBuilder de Borland, Visual Age de IBM,
Forte for Java de SUN, etc. :
• BeanBox est un outil extrêmement rudimentaire distribué avec le BDK (Beans Development Kit) à des fns exclusivement tutoriales et démonstratives,
• Bean Builder, également tutorial et gratuitement distribué par Sun, est nettement plus perfectionné.
6
Par exemple, l’existence de la méthode «
setPoids » implique l’existence de la propriété «
notez bien que le P majuscule est devenu minuscule.
Présentation de la technologie JavaBeans
poids ». Attention,
12
6. Persistance
Un objet est dit persistant si son état peut être maintenu entre deux exécutions du programme. Cas particulier intéressant : un objet dont l’état existe avant que le programme ne commence.
Plus terre-à-terre, un objet persistant est un objet qui (a) se laisse enregistrer, on dit sérialiser, dans un fchier et
(b) peut être ultérieurement recrée à partir de sa version sérialisée.
Par défaut, en Java sont sérialisables les données de types primitifs, les chaînes et les tableaux, ainsi que les objets dont
les membres ont des types sérialisables. C’est déjà un important service rendu au programmeur, car il s’agit d’un travail
complexe, du même genre de complexité que recycler la mémoire (garbage collector) : il faut parcourir les graphes,
souvent cycliques, que sont les objets, sans en oublier des branches et sans sombrer dans des boucles infnies
6.1. Flux d’entrée-sortie
Dans l’arbre d’héritage, deux classes sont les racines des sous-arbres les plus intéressants pour les entrées/sorties :
•
OutputStream
Super-classe de toutes les classes qui représentent des fux d’octets en sortie.
•
InputStream
Super-classe de toutes les classes qui représentent des fux d’octets en entrée.
Ce sont deux classes abstraites, indépendantes de la destination [resp. la source] des octets qu’elles véhiculent. Ici, nous
allons utiliser deux sous-classes respectives qui fxent cet aspect de la question :
•
FileOutputStream
Classe (concrète) représentant un fux d’octets aboutissant à un fchier.
•
FileInputStream
Classe (concrète) représentant un fux d’octets provenant d’un fchier.
Très naturellement, chacune de ces deux classes possède un constructeur prenant pour argument un nom du fchier qui
effectue l’ouverture du fchier (l’équivalent de l’opération fopen du langage C).
Ces classes spécifent où vont [resp. d’où viennent] les octets, mais elles ne fournissent pas de code intéressant pour
assembler les octets et en faire des données de niveau supérieur. Pour cela, il faut raccorder (comme on raccorde des
tuyaux quand on fait de la plomberie) leurs instances à des « fltres » plus évolués :
•
DataOutputStream
Les instances de cette classe écrivent des données de tous les types primitifs dans un
fux d’octets.
Attention, il ne s’agit pas d’une écriture formatée – comme celle qu’on obtient sur un
objet PrintStream avec les méthodes print et println – mais d’une écriture
binaire : les données ainsi écrites ne pourront être fablement exploitées qu’à travers un
objet DataInputStream.
•
DataInputStream
Les instances de cette classe lisent des données de tous les types primitifs depuis un
fux d’octets. Il s’agit de données écrites à l’aide d’un objet DataOutputStream.
•
ObjectOutputStream
Les instances de cette classe sérialisent des objets pour les envoyer vers un fux d’octets
en sortie.
•
ObjectInputStream
Les instances de cette classe dé-sérialisent (c’est-à-dire reconstruisent) des objets préalablement sérialisés dans un fux d’octets.
Comme on l’a dit, ces quatre classes ne sont pas concernées par la nature du support externe des octets lus ou écrits ;
leurs instances se construisent au-dessus d’un fux d’octets préalablement créé, qui règle cet aspect de la question.
6.2. Sérialisation des objets
Pour qu’une classe soit sérialisable elle doit implémenter l’interface Serializable. C’est une interface vide, dite « de
marquage » : on doit écrire l’énoncé implements Serializable dans la déclaration d’une classe pour faire savoir
qu’on autorise la sérialisation de ses objets, mais cela n’entraîne l’obligation d’écrire aucune méthode.
Présentation de la technologie JavaBeans
13
Exemple
Le programme suivant, purement démonstratif, construit une liste chaînée circulaire
sérialise dans un fchier nommé objet.fic. Fichier Maillon.java :
7
comportant quatre maillons et la
import java.io.Serializable;
class Maillon implements Serializable {
int val;
Maillon suiv;
Maillon(int v, Maillon s) {
val = v;
suiv = s;
}
}
Fichier Congelation.java :
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Congelation {
public static void main(String args[]) {
Maillon dernier = new Maillon(4, null);
Maillon premier =
new Maillon(1, new Maillon(2, new Maillon(3, dernier)));
dernier.suiv = premier;
try {
FileOutputStream fichier = new FileOutputStream("dezobjets.fic");
ObjectOutputStream sortie = new ObjectOutputStream(fichier);
sortie.writeObject(premier);
sortie.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Voici un programme qui reconstitue la liste sérialisée. Fichier Decongelation.java :
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Decongelation {
public static void main(String args[]) {
Maillon premier = null, p;
try {
FileInputStream fichier = new FileInputStream("dezobjets.fic ");
ObjectInputStream entree = new ObjectInputStream(fichier);
premier = (Maillon) entree.readObject();
entree.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
p = premier;
do {
System.out.print(p.val + " ");
p = p.suiv;
} while (p != premier);
}
}
7
Ici nous penons une liste circulaire pour rendre la démonstration plus concluante, mais cela n’a pas une grande utilité.
Cependant, en réféchissant à l’implémentation en mémoire des objets Java, on s’aperçoit que dès que ces derniers sont
un peu complexes, ils sont forcément réalisés par des structures pointées formant des graphes cycliques. Leur parcours
est donc aussi « piégé » que celui d’une liste circulaire…
Présentation de la technologie JavaBeans
14
Question intéressante : que se serait-il passé si la défnition de la classe Maillon avait changé entre la sérialisation et la
dé-sérialisation de la liste chaînée ? Bien entendu, Java vérife cela ; ainsi, l’expression
(Maillon) entree.readObject()
peut lancer une exception ClassNotFoundException (attrapée par le catch). Pour décider d’accepter ou non
l’expression précédente, java utilise des critères complexes dont la description dépasse le cadre de cet exposé. Voici
seulement quelques éléments de réponse :
• Lors de la sérialisation d’un objet, une certaine description de sa classe (un objet
java.io.ObjectStreamClass) est sauvegardée avec les données. Cela permet à Java de vérifer que la classe C
1
utilisée lors de la dé-
sérialisation est compatible avec la classe C 0 en vigueur lors de la sérialisation.
•
La notion de classes compatibles est subtile. Ce n’est pas l’égalité : C 1 peut être différente de C 0 ; ce n’est pas une
notion chronologique : C 1 n’a pas à être plus récente, ni plus ancienne, que C 0. Par exemple, deux classes ayant les
mêmes variables d’instance mais pas dans le même ordre, sont compatibles.
•
Par défaut, Java peut trouver incompatibles des classes qu’on souhaiterait compatibles. Un mécanisme assez rudimentaire de « numéro de version unique » (ou serialVersionUID) permet au programmeur d’indiquer, à ses
risques et périls, qu’une nouvelle classe C 1 est compatible avec une classe C 0 existante. Pour cela, il faut employer
l’utilitaire serialver pour obtenir la valeur n du serialVersionUID de C 0, puis déclarer dans C 1 une constante
statique nommée serialVersionUID et valant n.
6.3. Sérialisation explicite
La sérialisation par défaut que fait Java ne convient pas toujours. Certaines données ne peuvent pas être sérialisées, ou
ne le doivent pas. C’est le cas en particulier
• des variables static (variables de classe)
•
des variables d’instance qualifées transient (transitoires)
Le programmeur peut intervenir dans la sérialisation des objets d’une classe, spécifant en quoi consiste la sérialisation
des membres transient et laissant Java se charger de la sérialisation des autres. Pour cela il sufft d’écrire, dans la
classe en question (qui doit toujours se déclarer comme une implémentation de
Serializable), une méthode de la
forme
private void writeObject(ObjectOutputStream flux) throws java.io.IOException {
flux.defaultWriteObject();
...
}
Une telle méthode sera automatiquement appelée lors de la sérialisation de l’objet auquel elle appartient. Elle doit
commencer par appeler defaultWriteObject8, qui se charge de sérialiser tous les membres non statiques et non
transitoires de la classe. Ensuite, on décrit dans writeObject ce qu’il convient de faire avec les autres membres.
Bien entendu, on retrouve un processus symétrique lors de la dé-sérialisation, et notamment une méthode :
private void readObject(ObjetInputStream flux) throws java.io.IOException {
flux.defaultReadObject();
...
}
Exemple
Proposons-nous de rendre sérialisable une classe Temperature analogue à la classe PointChaud de l’exemple du
§ 3.5. Une instance de cette classe comporte une liste d’auditeurs intéressés par les changements de température. Or, il
n’y a aucune raison de penser que tous ces auditeurs, a priori des objets développés indépendamment de notre classe,
sont sérialisables.
La stratégie adoptée ici est simple : lors de la sérialisation d’un objet
sérialisables et nous ignorons les autres.
Temperature, nous sérialisons ses auditeurs
import java.io.*;
import java.util.*;
8
defaultWriteObject n’est pas une méthode normale. Elle ne peut être appelée que depuis la méthode
writeObject d’une classe sérialisable, généralement comme première instruction de cette méthode.
Présentation de la technologie JavaBeans
15
public class SimpleTemperature implements Serializable {
protected double tempCour = 18;
protected transient Vector auditeurs = null;
public synchronized void addTempChangeListener(TempChangeListener l) {
...
}
public synchronized void removeTempChangeListener(TempChangeListener l) {
...
}
private void writeObject(ObjectOutputStream sortie) throws IOException {
sortie.defaultWriteObject();
Vector v = null;
synchronized (this) {
if (auditeurs != null)
v = (Vector) auditeurs.clone();
}
if (v != null) {
Iterator iter = v.iterator();
while (iter.hasNext()) {
TempChangeListener l = (TempChangeListener) iter.next;
if (l instanceof Serializable)
sortie.writeObject(l);
}
}
sortie.writeObject(null);
// marque de fin
}
private void readObject(ObjectInputStream entree) throws IOException {
try {
entree.defaultReadObject();
Object l;
while((l = entree.readObject()) != null)
addTempChangeListener((TempChangeListener) l);
}
catch (ClassNotFoundException e) {
throw new IOException();
}
}
...
}
6.4. Objets « externalisables »
Les objets qui implémentent l’interface java.io.Externalizable contrôlent complètement leur sérialisation : ils ne
bénéfcient d’aucun mécanisme implicite. Les classes de tels objets comportent les méthodes
writeExternal et
readExternal selon le schéma suivant :
class ... implements java.io.Externalizable {
...
public void writeExternal(ObjectOutput flux) throws java.io.IOException {
... // ecriture de l’objet this dans le flux indiqué
}
public void readExternal(ObjetInput flux) throws java.io.IOException {
... // lecture de l’objet this depuis le flux indiqué
}
...
}
Lorsqu’un objet implémentant l’interface Externalizable est sérialisé, sa méthode writeExternal est appelée. De
la même manière, la dé-sérialisation d’un tel objet est entièrement assurée par la méthode readExternal.
6.5. Instanciation de beans sérialisés
Le mécanisme de la sérialisation fournit un moyen original pour instancier les beans : au lieu d’appeler un constructeur
de la classe du bean on peut dé-sérialiser une instance préalablement sérialisée.
Présentation de la technologie JavaBeans
16
Conséquence intéressante : le bean ainsi créé possèdera tout de suite l’état du bean original au moment de sa sérialisation, du moins pour ce qui concerne ses membres sérialisables. Ce peut être un état complexe, résultat d’un important
travail de personnalisation, effectué souvent à l’aide d’un outil de conception graphique.
Par exemple, supposons posséder une classe Gadget représentant un composant complexe. Supposons aussi que dans
une certaine application on a effectué les opérations suivantes :
9
class Confiture {
public static void main(String args[]) {
Gadget unGadget = new Gadget(...);
... travail de personnalisation de unGadget ...
try {
FileOutputStream fichier = new FileOutputStream("MonGadget.ser");
ObjectOutputStream sortie = new ObjectOutputStream(fichier);
sortie.writeObject(unGadget);
sortie.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
A la suite de l’exécution de ce programme, le fchier MonGadget.ser contient un bean sérialisé, qui peut être utilisé
par d’autres programmes pour instancier des objets de la classe Gadget, selon le schéma suivant :
...
try {
Gadget g = (Gadget) Beans.instantiate(null, "atelier.outils.MonGadget");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
...
(le premier argument de Beans.instantiate est le chargeur de classes souhaité ;
null indique le chargeur de
classes par défaut). Dans l’utilisation précédente, le bean sérialisé sera recherché dans un fchier nommé (sur UNIX)
atelier/outils/MonGadget.ser. Si ce fchier n’est pas trouvé, alors le bean sera créé par un appel du constructeur
par défaut (qui doit exister) de la classe Gadget.
7. Archives jar
La dernière étape dans la fabrication d’un bean est le rangement de ses fchiers .class et/ou de son fchier .ser, seuls
ou avec d’autres fchiers annexes, dans une archive appelée fchier jar, produite à l’aide de l’utilitaire jar.
Les archives jar sont compressées : ce sont des archives au format zip contenant (dans un répertoire nommé META-INF)
un « manifeste » qui est un fchier de texte décrivant le contenu de l’archive.
La syntaxe générale de la commande jar est
jar {ctx}[vmf...] [fichier manifeste] [fichier jar] sources...
On doit trouver une et une seule des options ctx :
commande la création d’un fchier jar nouveau,
commande le listage du contenu d’un fchier jar existant, sans création de fchier,
t
commande l’extraction du contenu d’un fchier jar existant.
x
Les autres options sont facultatives :
commande un travail « verbeux »,
v
signale la présence du nom du fchier à créer ou à exploiter,
f
signale la présence du nom du fchier manifeste.
m
9
Il semble que dans les milieux branchés Java, un bean sérialisé est dit « conft ». Comme exemples de tels beans on
cite alors la classe Pickle (petit légume conft dans du vinaigre aromatisé, utilisé comme condiment).
Présentation de la technologie JavaBeans
17
Exemple typique : création d’une archive MonGadget.jar comportant le fchier manifeste MonGadget.mf et tous les
fchiers .class du répertoire de travail :
jar
–cvmf
MonGadget.MF
MonGadget.jar
*.class
(nous avons mis le nom du fchier manifeste devant celui du fchier jar car dans les options m vient devant f).
Le fchier manifeste
Ce fchier renseigne sur les propriétés du fchier jar (version, signature, « classpath » requis, etc.) et sur le rôle des
fchiers qu’il contient. Il est fait d’une section principale qui concerne l’archive tout entière, suivie de plusieurs sections
individuelles concernant les différents fchiers de l’archive. Les sections sont faites de couples
<attribut>: <valeur>
Elles sont séparées entre elles par une ou plusieurs lignes blanches. Les sections individuelles commencent par un
couple qui précise le fchier visé par la section :
Name: <nom du fichier>
Chaque outil amené a lire un fchier manifeste n’y trouve que les informations qui l’intéressent. Il est convenu que,
dans tous les cas, les informations non comprises doivent être ignorées.
Exemples :
1. La soction principale commence toujours par une ligne qui renseigne sur la version du manifeste :
Manifest-Version: 1.0
1. Pour indiquer qu’un fchier jar contient les classes constituant une application dont le point d’entrée est la fonction
main de la classe Flageolet, la section principale comportera la ligne :
Main-Class: Flageolet
Cela permettra d’exécuter la classe exécutable Flageolet en exécutant la commande
java –jar <archive>.jar
N.B. Si la ligne Main-Class: a été omise, on peut quand même exécuter la classe exécutable Flageolet à partir de
son fchier jar par une commande de la forme (au lieu de classpath on peut mettre cp) :
java –classpath <archive>.jar Flageolet
2. Pour indiquer (par exemple à l’intention d’un outil de développement) que le fchier
bean :
Haricot.class contient un
Name: Haricot.class
Java-Bean: True
8. Quelques références
Patrick Niemeyer & Jonathan Knudsen
Introduction à Java
O’Reilly, 2000
Robert Englander
Java Beans, Guide du programmeur
O’Reilly, 1997
Chez Sun Microsystems :
JavaBeans™ API specifcation, version 1.01
http://java.sun.com/products/javabeans/docs/spec.html
The Java™ Tutorial. Trail : JavaBeans™
http://java.sun.com/docs/books/tutorial/javabeans
Tutorials & Training – Beans
http://developer.java.sun.com/developer/onlineTraining/Beans
Présentation de la technologie JavaBeans
18
Téléchargement