La réflexion
Introduction
Nous allons maintenant nous intéresser à une autre partie de la programmation objet dénommée - avec plus ou moins de réussite - la réflexion. La "réflexion" en POO n’est pas le fait de réfléchir profondément à quelque chose. Non, il s’agit ici d’une radioscopie d’objets inconnus et compilés qui va nous permettre de découvrir à la volée leurs propriétés et leurs comportements.
Il faut donc plutôt associer "reflet" plutôt que "méditation" à cette "réflexion"…
Mais pour quoi faire ?
L’interrogation est légitime : pourquoi avons-nous besoin de récupérer dynamiquement les caractéristiques d’objets ?
Deux exemples :
-
Le "plug-in"
Vous avez conçu une application ludique permettant aux petits et grands de se cultiver. L’application permet au joueur de s’identifier, de choisir un sujet à travailler et… de s’amuser (le plus important pour apprendre). L’application mesure alors des temps d’apprentissage, d’exercices, comptabilise des scores, les compare et délivre même des récompenses, etc. Un succès !
Les modules pédagogiques ne sont pas encore tous développés, car vous avez besoin du concours de spécialistes en géographie, en histoire, en mathématiques, etc. Les joueurs se verront proposer en téléchargement ces nouveaux modules au fur et à mesure de leur disponibilité. Une fois récupérés, votre application devra les reconnaître et les intégrer alors même qu’elle en ignorait l’existence quelques secondes plus tôt ! Mais alors comment ?
La "réflexion" sera une solution élégante, car elle permettra à votre application de parcourir le répertoire de téléchargement, de "radioscoper" dynamiquement...
Introspection d’une classe Java
Grâce à la réflexion, nous allons pouvoir récupérer dynamiquement depuis un objet :
-
le nom de la classe qui est à son origine,
-
si cette classe est d’accès public, privé, etc.,
-
quelle est sa classe parent,
-
quelles interfaces sont supportées par cette classe,
-
son ou ses constructeurs,
-
ses méthodes,
-
ses propriétés,
-
des informations sur son package d’appartenance et ses commentaires,
-
etc.
On parle ici de "métadonnées", c’est-à-dire d’informations décrivant un type d’objet.
Les environnements de développement utilisent la réflexion pour vous assister dans l’écriture de vos programmes.
C’est la classe java.Lang.Class qui joue le rôle de "collecteur d’informations" entre un type d’objet et votre programme. Tous les objets Java - y compris les plus primitifs - appartiennent à une java.Lang.Class et on va tout de suite radioscoper celle de String et lui demander la liste de toutes les méthodes.
package demo.reflexion;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) {
Class stringClass = String.class;
Method[] methods = stringClass.getMethods(); ...
Chargement dynamique et utilisation d’une classe découverte
La première étape de notre projet consiste à définir le ticket d’entrée à notre super système de plug-in, à savoir un contrat que tout module devra implémenter pour prétendre être appelé.
Pour cela, nous allons créer un package contenant uniquement une interface comme celle-ci :
public interface IPlugin {
String VoilaMonNom();
Boolean JoueAvecMoi();
}
Nous allons transformer le fichier .class généré en sortie de ce projet par un fichier .jar qui est le conteneur de livraison des fichiers Java compilés.
Pour cela, nous allons activer le menu File, option Project Structure, sélectionner Artifacts, puis, en cliquant sur le bouton +, ajouter un JAR contenant notre module et ses dépendances.
La construction du fichier .jar s’effectue depuis le menu Build, option Build Artifacts.
La première étape est maintenant terminée : nous avons un fichier InterfacePlugin.jar contenant le contrat. Nous allons maintenant nous placer côté créateur de plug-ins et nous allons créer une classe qui va implémenter cette interface.
Dans ce nouveau projet, pour pouvoir écrire une classe qui implémente une interface, il faut ajouter sa librairie.
Pour cela, nous allons activer le menu File, option Project Structure, sélectionner Libraries et, en cliquant sur le bouton +, ajouter la librairie Java.
Ensuite, nous pouvons écrire le code de notre plug-in :
import java.io.IOException;
public class Pluginfo implements IPlugin {
public Pluginfo(){
}
@Override
public String VoilaMonNom() {
return "Questionnaire Informatique";
}
@Override
public Boolean JoueAvecMoi() {
...
Exercice
Créez un nouveau projet contenant une classe implémentant l’interface IPlugin. Recopiez son fichier .jar dans le répertoire des plug-ins utilisé précédemment et vérifiez qu’il est bien reconnu et exploité par l’application ChargeurPlugin.
Voici un exemple console de ce que cela pourrait donner :
Le projet "PluginMusique" complet se trouve dans le fichier ZIP associé à cet ouvrage, que vous pourrez télécharger depuis la page Informations générales.
Privé, mais pas tant…
Nous avons vu qu’il fallait "masquer" à grand renfort d’attributs private certains membres de nos classes pour les protéger d’utilisations malvenues d’autres développeurs. Tout cela est bien remis en question avec la réflexion !
Ajoutez à un des plug-ins précédemment développés la méthode suivante :
private void IciCestPrivate(){
System.out.println("Cet accès est pourtant interdit");
}
Ajoutez au gestionnaire de plug-ins la séquence suivante :
Method privateMeth = o.getClass().getDeclaredMethod("IciCestPrivate");
if( privateMeth != null) {
privateMeth.setAccessible(true);
privateMeth.invoke(o);
}
Relancez l’application et constatez.
La réflexion est décidément puissante et permissive, mais ce n’est pas fini… En plus de découvrir les membres des classes, elle nous permet également de lire le code source.
Décompilation et obfuscation
Même si cela sort un peu du cadre de ce livre, cette dernière section traite de la protection de nos développements contre la copie. Comme évoqué précédemment, les .jar et autres .class peuvent être "facilement" convertis en code source Java par des outils adéquats. Ces outils sont appelés des "décompilateurs". Ils se présentent sous forme d’applications à installer sur votre machine ou encore sous forme de sites Internet. Nous allons utiliser l’un d’eux : http://www.javadecompilers.com
Rappelez-vous le petit exercice du chapitre Héritage et polymorphisme sur les comptes bancaires.
Nous allons tout d’abord prendre ses fichiers .class - donc fichiers binaires générés par IntelliJ IDEA - pour les passer au décrypteur en ligne.
Connectez-vous sur le site http://www.javadecompilers.com.
Sur l’écran d’accueil, cliquez sur le bouton Choisir un fichier ou faites glisser l’un des fichiers .class dans cette zone.
Cliquez sur Upload and Decompile (transférer et désassembler).
Au bout de quelques instants, le travail est terminé et vous pourrez cliquer sur le bouton Save.
Le code décompilé est déposé gracieusement sur votre PC dans un fichier .zip. À l’intérieur, le code source récupéré est celui-ci :
package labcomptebancaire;
public class CompteBancaire
{
private static Integer numCount = Integer.valueOf(100);
private String Titulaire;
private Integer Numero;
private double Solde;
public String getTitulaire() { return Titulaire; }
public final void setTitulaire(String Titulaire) {
this.Titulaire = Titulaire;
}
public Integer getNumero()
{
return Numero;
}
public final void setNumero(Integer Numero) { this.Numero = Numero; }
public double getSolde()
{
return Solde;
}
public void...