0% ont trouvé ce document utile (0 vote)
3 vues11 pages

Java Rmi Workflow

Ce document traite des bonnes pratiques pour la programmation distribuée avec Java RMI, en expliquant les concepts fondamentaux tels que les objets distants, les stubs, et la sérialisation. Il décrit également le workflow de développement d'une application distribuée, en détaillant les projets nécessaires pour le client et le serveur, ainsi que les erreurs fréquentes à éviter. Enfin, il présente des astuces pour optimiser la conception et la gestion des applications Java RMI.

Transféré par

Minh Tu Le
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
3 vues11 pages

Java Rmi Workflow

Ce document traite des bonnes pratiques pour la programmation distribuée avec Java RMI, en expliquant les concepts fondamentaux tels que les objets distants, les stubs, et la sérialisation. Il décrit également le workflow de développement d'une application distribuée, en détaillant les projets nécessaires pour le client et le serveur, ainsi que les erreurs fréquentes à éviter. Enfin, il présente des astuces pour optimiser la conception et la gestion des applications Java RMI.

Transféré par

Minh Tu Le
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Contents

1 Astuces et bonnes pratiques pour la programmation distribuée


avec Java RMI 1
1.1 Rappels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Workflow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.1 Projets de l’application . . . . . . . . . . . . . . . . . . . 4
1.2.2 Objets distants . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.3 Serveur . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.2.4 Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2.5 Gestionnaire et politique de sécurité . . . . . . . . . . . . 12
1.2.6 Codebases . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3 Erreurs fréquentes . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.3.1 Conflit entre les instances du serveur . . . . . . . . . . . . 16
1.3.2 Lancer le programme d’un composant dans le mauvais ordre 17
1.3.3 Création d’un projet Java modulaire . . . . . . . . . . . . 17
1.3.4 Erreurs liées aux codebases . . . . . . . . . . . . . . . . . 19
1.4 Astuces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.4.1 Rappels pour la POO en Java . . . . . . . . . . . . . . . . 19
1.4.2 Divers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Figure 1: Enregistrement d’un OD

1 Astuces et bonnes pratiques pour la program-


mation distribuée avec Java RMI

1.1 Rappels

La programmation en Java RMI permet à un objet s’exécutant dans une JVM


d’invoquer des méthodes sur un objet s’exécutant dans une autre JVM (i.e., un
objet distant s’exécutant dans un autre processus) et donc de construire des
applications Java distribuées. Elle permet d’abstraire les détails de connexion,
de transport, et de sérialisation/désérialisation des objets communiquant. Du
point de vue de l’utilisateur, ceci se traduit par une interaction avec l’objet
distant comme s’il était local (i.e., dans la même JVM que l’objet appelant).
Les composants figurant dans une application Java RMI sont le client, le serveur
et son squelette, le registre RMI, les objets distribués et leurs stubs (proxies), et
les codebases. Chaque entité ainsi devra avoir sa propre description et son propre
rôle afin de pouvoir l’exploiter correctement et d’assurer une bonne conception
de l’application.

Figure 2: Récupération du stub

1 2
1.2 Workflow

1.2.1 Projets de l’application

Dans un environnement distribué, les composants d’une application distribuée


tournent dans des processus différents, souvent localisés sur plusieurs machines.
Dans le cadre de ce TP on créera un environnement distribué sur la même
machine, en affectant à chaque composant de l’application son propre projet,
tournant dans son propre processus. Pour distribuer notre application sur la
même machine, on divise notre projet en trois :
1. un projet common contenant toutes les entités accessibles au serveur et
client, telles que les interfaces des objets distants ([Link] ou
toute sous-interface de celle-ci) et éventuellement des classes désignant
des objets non distants mais sérialisables (implémentant l’interface
[Link]).
2. un projet client contenant la classe Client et la/les classe(s) qui de-
vrai(en)t être reconnue(s) par le serveur via le codebase.
3. un projet serveur contenant la classe Serveur et toutes les classes des
objets distants, étendant [Link] et im-
Figure 3: Invocation d’une méthode sur l’OD plémentant chacune l’interface correspondante de son objet distant (i.e.,
la classe ObjectDistantImpl implémente l’interface IObjetDistant).
Pour que les projets du serveur et du client puissent reconnaître les entités
communes dans le projet common, il faut ajouter le build path de ce dernier
à leurs build paths. Pour ce faire : clic droit sur le projet client/serveur →
Build Path → Configure Build Path... → Sous l’onglet Projects clic sur
Classpath → Add → ajouter le projet common.

1.2.2 Objets distants

[Link] Définition
un objet distribué est un objet qui implémente une interface distante (i.e.,
l’interface [Link] ou n’importe quelle sous-interface personnalisée de
celle-ci) et qui éventuellement héritera de [Link]
(bonne pratique pour hériter le comportement d’exportation de l’objet).
Ceci nous permettra de référencer notre objet distant au sein d’un reg-
istre RMI qui sera ensuite “queriable” par un client via la méthode
[Link]#lookUp(String name).
Chaque méthode d’une interface distante doit déclarer l’exception RemoteException
dans sa clause throws lors de la déclaration de sa signature. Cette exception
permet de capturer les différentes erreurs pouvant survenir lors de l’exécution
Figure 4: Passage d’un paramètre d’une classe inconnue du serveur dans un environnement distribué. Par exemple, une exception retournée lors de
la coupure de la connexion entre les sockets utilisées pour établir une connexion
réseau, ou lors de l’accès distant à une référence non existante, . . .

3 4
/* SYNTAXE GÉNÉRIQUE D'UNE INTERFACE DISTANTE */ sera retourné par le registre RMI. Alors, on ne peut pas faire passer un objet
// dans le projet commons non distant par référence en Java RMI. On ne peut le faire que “par valeur”.
package [Link]; Pour ce faire, il faut alors le rendre sérialisable, et cet objet sérialisable ne sera
associé à aucun proxy le référençant.
import [Link];
import [Link];
[Link] Sérialisation vs. Désérialisation
public interface IObjetDistant extends Remote { Un objet sérialisable est un objet qui possède une représentation textuelle perme-
typeRetour methodeDistante([params]) throws RemoteException; ttant de le stocker sur des supports de persistance, mais aussi de le transmettre
... à travers un réseau. Désérialiser un objet consisterai ainsi à le reconstruire à par-
} tir de sa représentation textuelle. En Java, rendre un objet sérialisable signifie
qu’il doit implémenter l’interface [Link].
/* SYNTAXE GÉNÉRIQUE D'UNE CLASSE D'UN OBJET DISTANT */
// dans le projet serveur
package [Link]; [Link].1 Exemple
public class Person implements Serializable {
import [Link]; /* ATTRIBUTES */
import [Link]; private String name;
private int age;
import [Link];
/* CONSTRUCTORS */
public class ObjetDistantImpl extends UnicastRemoteObject { public Person() {
/* attributs */
/* méthodes */ }
@Override
typeRetour methodeDistante([params]) throws RemoteException{ public Person(String name, int age) {
// implémentation [Link] = name;
} [Link] = age;
... }
// autres méthodes : getters/setters, implémentations privées internes, etc.
} /* METHODS */
public String getName() {
[Link] Passage par référence et passage par valeur return name;
}
En général, un passage par valeur veut dire passage par copie de l’objet lui-même,
alors qu’un passage par référence veut dire passage de l’adresse de l’objet au lieu public int getAge() {
de l’objet lui-même. Dans le cadre de Java, ceci est abstrait du programmeur. return age;
Tous les types primitifs (char, byte, int, long, double, boolean, . . .) sont }
passés en valeur, alors que tous les objets sont passés par référence.
@Override
public String toString() {
[Link] Passage par référence vs passage par valeur en Java RMI
return "(Name: "+name+", Age: "+age+")";
Dans le cadre du Java RMI faire un passage par référence signifie faire un passage }
d’un objet distribué qui sera référencé par son objet proxy correspondant et c’est }
ce dernier qui sera utilisé par toute entité souhaitant utiliser l’objet distant et qui

5 6
[Link].2 En Java RMI * a RealInternet concrete class that plays the role of RealSubject
* in the Proxy design pattern.<br/>
Quand un client essaye d’invoquer une méthode sur l’objet distant, le proxy
* It implements the Internet interface to allow access to the Internet.
sérialise (ou marshalle) les arguments passés et l’envoie au serveur. Quand
* @author anonbnr
le serveur reçoit du client une invocation distante, il l’envoie au squelette qui
*
désérialise (ou démarshalle) les arguments passés, invoque la méthode sur
*/
l’objet distant réel, et retourne le résultat au serveur qui le renvoie au client. La
public class RealInternet implements Internet {
sérialisation et la désérialisation des arguments passés sont automatiquement
gérées par Java RMI.
/* METHODS */
Pour plus d’informations sur la sérialisation/désérialisation en Java : @Override
[Link] public void connectTo(String serverHost) {
[Link]("Standard Console: Connecting to " + serverHost);
}
[Link] Les proxys }
Proxy est un patron de conception (design pattern) dans lequel un objet peut
être référencé par un autre objet qui se comporte en tant que son intermédiaire. package [Link];
Les deux types d’objets sont conformes à une interface commune. L’avantage
de cette conception est de pouvoir effectuer des contrôles et/ou des calculs import [Link];
avant/après l’invocation actuelle d’une méthode ciblée d’un objet. Toute méth- import [Link];
ode ciblée fait partie ainsi de l’interface commune d’un objet et de son proxy.
/**
* a ProxyInternet concrete class that plays the role of Proxy
[Link].1 Exemple * in the Proxy design pattern.<br/>
package [Link]; * It provides a proxy to classes implementing Internet, particularly
* to ban Internet connections to some hosts.
/** * @author anonbnr
* an Internet interface that plays the role of Subject *
* in the Proxy design pattern.<br/> */
* It provides an interface for connecting to the Internet public class ProxyInternet implements Internet {
* that we want to limit access to using a proxy.
* @author anonbnr /* ATTRIBUTES */
*/ /**
public interface Internet { * The proxied Internet connection
*/
/* METHODS */ private Internet internet;
/**
* Connects to serverHost /**
* @param serverHost An Internet host to which we wish to connect * The list of banned sites
*/ */
void connectTo(String serverHost); private static List<String> bannedSites;
}
static {
package [Link]; bannedSites = new ArrayList<>();
[Link]("[Link]");
/** [Link]("[Link]");
[Link]("[Link]");

7 8
[Link]("[Link]"); En Java RMI, les objets distants sont référencés par des stubs (proxy), qui
} seront liés à des noms uniques et enregistrés dans un registre RMI. Quand un
client réclame un objet distant par son nom au registre RMI, celui-ci lui renvoie
/* METHODS */ le stub associé à l’objet demandé. Ce stub sera ensuite utilisé par le client
/** pour invoquer des méthodes sur l’objet distant. Le stub se chargera ainsi de
* Only allows the proxied Internet connection to connect to hosts sérialiser les paramètres des invocations, et faire les appels réseaux nécessaires
* that are not in the banned sites, otherwise denies access to the host. pour communiquer avec le serveur. Lorsque le serveur reçoit l’appel, il l’envoie
* It also creates the Internet connection, only if it hasn't already au squelette qui désérialise les paramètres, invoque la méthode sur l’objet distant
* been created réel, et retourne le résultat au serveur qui le renvoie au client.
*/
Les objets stubs, le squelette, et leurs comportements sont automatiquement
@Override
générés et contrôlés par Java RMI et on ne s’en soucie pas. Ainsi, on continuera
public void connectTo(String serverHost) {
à construire notre programme client comme si on était en train d’invoquer une
if ([Link](serverHost))
méthode sur un objet local, alors qu’on est en train d’invoquer une méthode sur
[Link]("Standard Error: Access Denied to " + serverHost);
l’objet proxy d’un objet distant.
else {
if (internet == null) [Link] Dans le TP
internet = new RealInternet();
Dans le cadre de ce TP, un animal est un objet distant qui devrait être ma-
[Link](serverHost); nipulé par le client, et donc il faut qu’il implémente une interface distante
} (IAnimal qui dérive de l’interface [Link]) et qu’il étend la classe
} [Link]. Par contre, son espèce est un objet
} qui ne devrait pas être manipulé directement par le client, mais qui devrait
quand même être reconnue par lui lorsque ce dernier va manipuler des objets de
package [Link]; type IAnimal (des objets proxy). Pour ce faire, l’espèce doit être ainsi déclarée
en tant qu’une classe sérialisable dont l’implémentation doit être accessible au
/** client et au serveur. C’est pour cette raison qu’on l’installe au sein du projet
* a Test class commons.
* @author anonbnr
* 1.2.3 Serveur
*/
public class Test { Un serveur doit effectuer les fonctionalités suivantes :
public static void main(String[] args) {
Internet internet = new ProxyInternet(); • éventuellement créer une instance d’un registre RMI dans le même proces-
[Link]("[Link]"); sus : Registry registry = [Link](RMI_REGISTRY_PORT);
[Link]("[Link]"); si l’outil rmiregistry a été lancé déjà via la ligne de commande dans un
} processus séparé.
} • récupérer une instance du registre RMI : Registry registry =
getRegistry(RMI_REGISTRY_PORT);
/* OUTPUT */
// Standard Error: Access Denied to [Link] • créer les objets distants ;
// Standard Console: Connecting to [Link] • lier les objets distants à des noms uniques au sein d’un registre RMI ;
• recevoir une requête du client consistant à invoquer une méthode distante
sur l’objet distant via son proxy, la traiter, et retourner le résultat.
[Link].2 En Java RMI
Le serveur doit être mis en place dans l’ordre suivant :

9 10
1. mise en place de son gestionnaire de sécurité (et de sa politique de sécu- comme un contrôleur, faisant le lien entre les fonctionnalités du client et les
rité). interactions de l’utilisateur (i.e., une CLI ou un GUI ).
2. éventuellement mise en place de son codebase.
// Exemple
3. mise en place de sa politique de sécurité.
/*
4. mise en place de l’instance du registre RMI.
* Classe modélisant le contrôleur
5. instanciation et distribution d’objets distants dans le registre RMI.
* qui gère l'interaction entre le client
* et le serveur.
[Link] Serveur et Main */
public class Main {
En général, la méthode main() (côté serveur) est ajouté à la classe du serveur public static void main(String[] args) {
directement. Toutefois, ceci peut devenir limitant pour l’extensibilité et la réutil- try {
isabilité, surtout si on souhaite éventuellement avoir une hiérarchie de serveurs
de différentes fonctionnalités et qui peuvent partager des fonctionnalités com- Client client = new Client();
munes par héritage. Pour éviter ces limitations, on peut introduire une classe [Link]();
Main dédiée, qui contiendra la méthode main() créant et mettant en place une [Link]("Animal Care");
instance du serveur. ICabinet cabinet = [Link]();
// Exemple [Link]("Objet proxy de l'objet cabinet: "+cabinet);
public class Main { /*
public static void main(String[] args) { * OUTPUT
Server server = new Server(); * ======
try { * Objet proxy de l'objet cabinet: Proxy[ICabinet,
[Link](); * RemoteObjectInvocationHandler [UnicastRef [liveRef:
} catch(RemoteException e) { * [endpoint:[[Link]:34553]
[Link](); * (remote),objID:[6cc201a2:17cc940e953:-7fff,
} * 604015115438757631]]]]]
} */
} browseCabinet(client, cabinet);

} catch (RemoteException e) {
1.2.4 Client [Link]();
} catch (NotBoundException e) {
Un client doit effectuer les fonctionnalités suivantes : [Link]();
• récupérer une instance du registre RMI. } catch (IOException e) {
• rechercher les objets distants d’intérêt. [Link]();
• invoquer des méthodes sur un objet distant pour accomplir une }
logique métier (e.g., afficher des informations sur un objet distant, }
créer/modifier/supprimer un objet distant, . . .) }

[Link] Client et Main 1.2.5 Gestionnaire et politique de sécurité

En général, la méthode main() (côté client) est ajoutée à la classe du client Un gestionnaire de sécurité pour une classe en Java permet de lui définir une
directement. Toutefois, pour les mêmes raisons susmentionnées, on peut in- politique de sécurité et de vérifier si cette politique de sécurité est bien respectée.
troduire une classe Main dédiée, qui contiendra la méthode main() créant et Par défaut, une classe n’a pas un gestionnaire de sécurité. Pour en créer un :
mettant en place une instance du client. De plus, cette classe se comportera [Link](new SecurityManager());

11 12
Une politique de sécurité permet de contrôler les interactions pos- Voici donc deux implémentations simples de ces deux méthodes au niveau de la
sibles avec une classe en spécifiant des permissions. Toute interac- classe CabinetMedicalImpl (côté serveur) implémentant ICabinetMedical :
tion non autorisée par la politique de sécurité engendre une excep-
@Override
tion de type [Link]. Par défaut, une
public boolean addAnimal(String name, String ownerName, String speciesName,
classe n’a ni un gestionnaire de sécurité, ni une politique de sécurité.
int speciesAverageLife, String race, String state)
Pour lier une classe à un fichier définissant sa politique de sécurité :
throws RemoteException {
[Link]("[Link]", "path/to/[Link]");
IAnimal patient = new AnimalImpl(name, ownerName, speciesName,
Dans le cadre de ce TP, on créera une politique de sécurité à titre indicatif en speciesAverageLife, race, state);
donnant toutes les permissions à tout le monde. Pour ce faire : return [Link](patient);
}
// path/to/[Link]
grant {
@Override
permission [Link];
public boolean addAnimal(String name, String ownerName, Species species,
};
String race, String state) throws RemoteException {
IAnimal patient = new AnimalImpl(name, ownerName, species, race, state);
1.2.6 Codebases return [Link](patient);
}
[Link] Motivation Voici les deux constructeurs de la classe AnimalImpl (côté serveur) implémen-
Dans une prochaine étape de l’exercice on introduit la notion d’un cabinet médi- tant IAnimal :
cal qui contiendra la liste des animaux suivis et qui sera le point d’interfaçage protected AnimalImpl(String name, String ownerName, String speciesName,
entre le client et le serveur. Le cabinet médical introduira des méthodes perme- int speciesAverageLife, String race, String state)
ttant de consulter les animaux suivis, mais aussi d’en ajouter. Ainsi, un cabinet throws RemoteException {
médical est un objet distant créé de la même manière que précédemment avec les [Link] = name;
objets animaux. On suppose que l’interface distante ICabinetMedical fournit [Link] = ownerName;
plusieurs méthodes dont les méthodes suivantes pour ajouter des animaux : [Link] = new Species(speciesName, speciesAverageLife); // création de l'espèce
boolean addAnimal(String name, String ownerName, String speciesName, [Link] = race;
int speciesAverageLife, String race, [Link] = new FollowUpFileImpl(state);
String state) throws RemoteException; }
boolean addAnimal(String name, String ownerName, Species species,
String race, String state) throws RemoteException; protected AnimalImpl(String name, String ownerName, Species species, String race,
String state) throws RemoteException {
La première permet d’ajouter un animal sans besoin de spécifier son espèce [Link] = name;
explicitement (via une instance de la classe Species). C’est-à-dire l’objet [Link] = ownerName;
Species est créé à l’intérieur du constructeur de l’animal et dont les paramètres [Link] = species; // affectation de l'espèce créée auparavant
String speciesName, int speciesAverageLife sont passés via la méthode [Link] = race;
addAnimal(). [Link] = new FollowUpFileImpl(state );
Par contre, la deuxième version de la méthode permet d’ajouter un animal en }
spécifiant explicitement son espèce via une instance de la classe Species. Ceci Maintenant supposons que le client souhaite ajouter au cabinet médical un
est possible parce que la classe Species est dans le projet commons, et donc le IAnimal dont l’espèce est une sous-classe de Species (e.g., une classe Dog).
client et le serveur la reconnaissent. Ainsi l’objet Species est créé et initialisé Le client peut déclarer ainsi une classe Dog de son côté qui étend Species et
du côté client avant d’être passé à la méthode addAnimal(). Le constructeur ensuite utiliser la deuxième version de addAnimal() en lui passant une instance
de l’animal ajouté se contentera ainsi d’affecter l’instance Species passée à de Dog pour ajouter l’animal au cabinet médical. Toutefois, le serveur va se
l’attribut correspondant au niveau de l’instance de l’animal créé. plaindre de son côté parce qu’il ne reconnaît pas la classe Dog. En effet, il ne
connaît que la classe Species dans le projet commmons qui est ajouté à son class-

13 14
path. Donc on aura une exception de type ClassNotFoundException quand on de chercher une classe du client qu’il ne reconnaît pas à partir du chemin du
essaye d’ajouter l’animal ayant une espèce Dog au cabinet médical. codebase, si on inclut le nom d’un package contenant la classe dans le chemin,
le serveur ne pourra jamais la trouver. Dans cette situation :

[Link] Solution : Les codebases 1. soit on spécifie le chemin du dossier bin/ du client comme codebase con-
tenant tous les fichiers bytecode de toutes les classes du projet client (ce
Pour résoudre ce problème, il faut que le serveur puisse reconnaître la classe qui peut être problématique si on ne souhaite pas exposer toutes les classes
Dog, notamment son bytecode (i.e., [Link]). Pour ce faire on utilise ce du client au serveur).
qu’on appelle un codebase désignant un mécanisme classpath distribué. Il 2. soit on peut copier les fichiers bytecode des classes que l’on souhaite ex-
s’agit simplement d’un dossier contenant des fichiers bytecode de classes. poser et les mettre dans un dossier parent (qui n’est pas un package) dans
Chaque entité pourra avoir son propre codebase où seront installés les fichiers le projet (ou ailleurs sur le système de fichiers). Ainsi, le chemin du
bytecode des classes qui devraient être accessibles aux autres composants de codebase désignera le chemin de ce dossier parent.
l’application. En gros, si le client définit et utilise des classes qui figurent
dans l’application et qui doivent être reconnues par le serveur, il installera leurs
[Link] Dernières remarques
fichiers bytecode dans son codebase, et le serveur ira pendant l’exécution consul-
ter le codebase du client, où il trouvera ces fichiers bytecode qui n’existent pas 1. Dans le cadre de ce TP nous avons créé un projet commons qui contiendra
directement dans son classpath. Bien sûr ça peut être l’inverse aussi ou le client les interfaces distantes et les classes non distantes accessibles au client et
ira chercher des classes qu’il ne reconnaît pas depuis le codebase du serveur. serveur, puis on a ajouté son classpath aux classpaths du client et serveur.
Cependant, ceci est une approche simplifiée pour le but du TP. En réalité,
Ce codebase peut être localisé sur une machine distante et servi par
ce projet sera un codebase dédié qui sera consulté par le client et le serveur
un serveur web via HTTP et qui pourra être consulté via son URL
pour récupérer les fichiers bytecode des interfaces distantes et les classes
(e.g., [Link] ), comme il peut être défini
non distantes qui leur sont accessibles.
comme dossier sur le système de fichiers de la même machine d’un
2. Le client ne se comportera pas comme un serveur que si le serveur a besoin
composant de l’application et pourra être consulté via son chemin (e.g.,
de manipuler explicitement des objets distants provenant du client, et dans
file:/home/someUser/path/to/codeBase).
ce cas là on peut parler d’un mode Peer-to-Peer (P2P). Si le serveur
Pour spécifier un/plusieurs codebases qui pourront être consultés par un com- se contente uniquement de récupérer le fichier bytecode d’une classe C
posant, on spécifie leurs URIs via la propriété "[Link]" du utilisée par le client et dont une instance est transmise au serveur sans
composant concernée de la manière suivante : [Link]("[Link]", que ce dernier puisse la reconnaître, ceci n’est pas du P2P. En effet, le
"URICodebase1 URICodebase2 ...");. Dans notre cas, il faut définir cette serveur ne manipule pas explicitement des variables de type C dans son
propriété pour le serveur, vu que c’est lui qui va aller chercher l’implémentation code.
de la classe Dog depuis le codebase du client. 3. Quand on a une application P2P, les interfaces des objets distants créés
par le client et qui seront manipulées explicitement par le serveur doivent
Il faut aussi faire attention en spécifiant le chemin du codebase; il faut que le
être dans le projet commons.
dossier du codebase ne soit pas un package contenant la classe qu’on souhaite
partager, parce que la classe est reconnue par son nom complètement qualifié
(Fully Qualified Name). 1.3 Erreurs fréquentes
Pour rappel, le nom complètement qualifié d’une classe désigne son nom et le
nom du package qui la contient (e.g., une classe Animal qui existe dans un pack- 1.3.1 Conflit entre les instances du serveur
age [Link]-package aura [Link]
comme nom complètement qualifié). Ceci est essentiel parce que deux classes Parfois vous faites tourner plusieurs instances du serveur par accident et vous
peuvent avoir le même nom tout en étant localisées dans deux packages tombez ainsi sur une erreur vous indiquant que le port de votre serveur est déjà
différents. Ainsi le seul moyen de les distinguer sera à travers leurs noms occupé, et qu’il ne peut pas être lancé. Dans cette situation, avant de relancer
complètement qualifiés. votre programme, vérifier bien dans votre console qu’aucune instance du serveur
n’est en train de tourner (i.e., elle est “terminated”). En gros, il faut arrêter
Dans un projet Java, tout dossier sous src/ ou bin/ est un package et donc ne toutes les instances du serveur avant de relancer une nouvelle instance.
peut pas être utilisé en tant qu’un dossier codebase. Alors si le serveur essaye

15 16
Figure 5: Consulter les programmes en cours d’exécution sous Eclipse

1.3.2 Lancer le programme d’un composant dans le mauvais ordre

Parfois vous essayez de lancer le programme de votre client alors que le pro-
gramme de votre serveur ne l’est pas encore. Dans ce cas le client ne pourra pas
ainsi se connecter au serveur. Rappelez-vous de l’ordre d’exécution :
1. lancer une instance du registre RMI :
• soit en utilisant rmiregistry depuis la ligne de commandes pour un
processus séparé,
• soit en créant une instance du registre au sein du processus du serveur
en utilisant createRegistry(int port), et qui sera ainsi lancée
quand vous lanceriez votre serveur.
2. lancer une instance du serveur.
3. lancer une instance du client.
Pour lancer plusieurs instances de votre client, il faut que le programme de
votre client soit terminable manuellement via son interface fournie (e.g., via une
option quit pour une CLI ou un bouton fermer pour une GUI).

1.3.3 Création d’un projet Java modulaire

Quand vous créez votre projet Java, le wizard de création vous demandera si
vous voulez créer un fichier [Link] qui permet de rendre votre projet
modulaire. Si vous le faites, votre projet n’arrivera pas à localiser le module
contenant les classes du package [Link] pour qu’il puisse se lancer. Dans ce
cas, vous pouvez préciser les modules nécessaires dont votre projet dépend selon Figure 6: Le wizard pour la création de projets Java sous Eclipse
la syntaxe de description des modules en Java (au délà de la portée de ce TP).
Je vous invite ainsi à éviter de créer un projet Java modulaire (cf. [Link] ci-joint),
sauf si vous maîtrisez déjà tous ses facettes et que vous le souhaitez, mais ce
n’est pas le but du TP.

17 18
3. on peut les déclarer protected si on souhaite donner un accès directe aux
sous-classes de C.

[Link] Constructeurs par défaut et constructeurs paramétrés


1. un constructeur par défaut est une méthode spéciale d’une classe qui per-
met d’en créer un objet vide, éventuellement avec des initialisations par
défaut pour ses attributs.
2. un constructeur paramétré est une méthode spéciale d’une classe qui per-
met d’en créer un objet et d’initialiser ses attributs par le biais de ses
paramètres.
3. quand une classe possède plusieurs attributs, on peut déclarer plusieurs
constructeurs paramétrés pour fournir plusieurs manières différentes de
créer et initialiser des objets.

[Link] Nommer ses éléments de programmation


1. conventions :
• camelCase pour les variables/attributs/méthodes : nomVariable,
Figure 7: Créer un projet Java non modulaire sous Eclipse nomChamps, nomMethode([params]).
• CamelCase pour les classes/interfaces/énumérations : NomClasse,
NomInterface, NomEnum.
1.3.4 Erreurs liées aux codebases 2. on essaye toujours de choisir des noms pertinents pour nos éléments de
programmation :
Quand vous spécifier l’ensemble des codebases consultables par des classes, vous • List liste = new ArrayList<>(); → non parce que le nom ne spécifie
pouvez éventuellement lancer des exceptions de types divers. Dans ce cas, véri- pas ce que la liste doit contenir
fiez que : • List animaux = new ArrayList<>(); → ok parce qu’on peut savoir
1. le nom de la propriété pour lier des codebases est bien formé. maintenant ce que cette liste contiendra.
2. l’URI (URL ou chemin dans le système de fichiers) est bien formé. • List listeAnimaux = new ArrayList<>(); → ok, bien que la partie
3. votre dossier existe et ne désigne pas un package pour les classes du code- “liste” n’est pas forcément nécessaire parce qu’on peut le savoir di-
base. rectement depuis la déclaration de la variable. Toutefois, ça peut
être utile si cette variable est utilisée dans un endroit dans le pro-
gramme loin de sa ligne de déclaration.
1.4 Astuces • List ListeAnimaux = new ArrayList<>(); → le nom choisi est ok
mais la convention de nommage n’est pas respectée (camelCase).
1.4.1 Rappels pour la POO en Java 3. parfois on est ramené à l’implémentation d’un concept en deux parties
: une interface et une classe l’implémentant. Si cette interface est
[Link] Principe d’encapsulation destinée à avoir une classe fournissant une implémentation par défaut
(e.g., implémentation du concept par un serveur ou une factory dédiée),
1. toute classe s’occupe de ses propres responsabilités, encapsule ses pro-
on adoptera l’un des styles de nommage suivants pour l’interface et la
priétés (attributs et méthodes), et impose des restrictions sur leur accès
classe respectivement :
pour les autres class.
• Concept/ConceptImpl : e.g., Animal/AnimalImpl, DossierSuivi/DossierSuiviImpl.
• IConcept/Concept : e.g., IAnimal/Animal, IDossierSuivi/DossierSuivi.
2. les attributs d’une classe C doivent toujours être private et accessibles
• IConcept/ConceptImpl : e.g., IAnimal/AnimalImpl, IDossierSuivi/DossierSuiviImpl.
uniquement à travers des getters (lecture) et éventuellement des setters
(écriture).

19 20
1.4.2 Divers

Il faut imaginer votre système d’une manière orientée objet. Pensez bien aux
patrons de conception, aux principes de conception SOLID, et aux relations
entre vos objets. Réifier quand c’est possible et nécessaire. Imaginer une inter-
face utilisateur (CLI ou GUI) qui nous permettra de tester votre travail d’une
manière ergonomique.
N’oubliez pas de commiter votre code sur un dépôt distant après avoir implé-
menté chaque feature. De cette manière vous diviserez vos tâches convenable-
ment et vous laisserez des traces pour suivre l’évolution de votre performance.

21

Vous aimerez peut-être aussi