jeudi 26 janvier 2017

Chargement dynamique des classes : le fondement des systèmes basés sur les plugins

Les systèmes basés sur le principe des plugins sont facilement extensibles. Malgré cela, il est rare de faire appel à cette approche dans le contexte des entreprises (sauf l'utilisation des plateformes très stable comme NetBeans Framework); les dépendances sont très faibles entre les composants et aucune vérification n'est pas possible avant l'exécution réelle du système. Dans un autre contexte, cette approche peut être vue comme la solution idéale pour concevoir un système.
Parmi ces contextes, nous trouvons le développement des outils. Un outil est censé offrir un ensemble de services ou de fonctions pour accomplir une tâche donnée mais sans faire partie du noyau du système qui doit rester toujours stable. Cette approche permettra d'ajouter un aspect dynamique pour l'extension et pour la configuration des outils développés.
Un autre contexte est le contexte de la recherche. Il n'est pas rare de se trouver dans la situation de test et d'évaluation de plusieurs approches, techniques ou algorithmes qui "font la même chose" mais avec des méthodes différentes. Avoir la possibilité de charger automatiquement, pendant l'exécution ou même pour la simple raison de ne pas toucher au noyau peut être très utile dans ce contexte.
La manipulation des classes et l'appellation des méthodes d'une méthode dynamique sont très courantes en Java. D'ailleurs, elles représentent le fondement des CDI (Context and Dependencies Injection) qui ont rendu la J2EE la plateforme la plus légère pour le développement entreprise.
La rubrique de base est la classe "Class" qui permet la manipulation des classes. Dans cet exemple, nous allons charger dynamiquement une classe en se basant sur son nom. Le nom doit être le nom complet comme s'il s'agit d'une instruction "import", le fichier jar qui contient la classe ne doit pas forcément être le jar du système, il suffit qu'il soit accessible (inclus dans la classpath) :

Class classeChargee = Class.forName(nom_de_la_classe);
Pour créer une instance, une méthode est déjà prête :

MaClasse operation = (MaClasse) classeChargee.newInstance();
Dans notre exemple, le service requis est le clacul de la somme de deux entiers (le service le plus simple au monde). Pour le définir, nous utilisons une interface (c'est la même idée utilisée dans les services web) :

public interface Somme {
    
    /**
     * Pour simplifier, il s'agit du calcul d'une somme.
     * @param a Le premier opérand.
     * @param b Le deuxième opérand.
     * @return  La somme des deux opérands
     */
    public int somme(int a, int b);
    
}
Pour voir au mieux l'avantage de la technique présentée, nous allons définir deux classes qui implémentent le service Somme : Somme1 et Somme2.

public class Somme1 implements Somme{

    @Override
    public int somme(int a, int b) {
        System.out.println("Calcul en utilisant Somme1");
        return a + b;
    }
    
}


public class Somme2 implements Somme{

    @Override
    public int somme(int a, int b) {
        System.out.println("Calcul en utilisant Somme2");
        return a + b;
    }
    
}
Pour simplifier le code, nous allons donner les noms des classes dynamiquement par saisie. Il est possible de scanner tel ou tel dossier pour récupérer la liste des fichiers (c'est pour cela qu'il faut mettre les plugins dans le même dossier) ou bien de mettre la liste des classes dans un fichier texte. La méthode main sera ainsi :

public static void main(String[] args) {

        if (args.length != 2) {
            // Nous attendons deux nombres entiers pour calculer la somme.
            System.out.println("Usage CalculerSomme nbr1 nbr 2\nExemple : CalculerSomme 12 25");
        } else {

            int a = Integer.parseInt(args[0]);
            int b = Integer.parseInt(args[1]);
            
            Scanner entree = new Scanner(System.in);
            String laClasse;
            while (true) {

                laClasse = entree.nextLine();

                try {
                    Class classeChargee = Class.forName(laClasse);
                    Somme operation = (Somme) classeChargee.newInstance();

                    System.out.println("La somme de a et b est : " + operation.somme(a, b));
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
                    ex.printStackTrace();
                }

            }

        }
    }
L'exécution montre le résultat de la saisie du nom de la classe Somme1 ensuite le nom de la classe Somme2 :

Le code source est téléchargeable par ici.