TD3 - Chargement dynamique

De $1

Ce TP a pour objectif d'étudier les mécanismes de chargement dynamique en Java et en particulier le fonctionnement de la classe ClassLoader.

MyClassLoader

On veut créer son propre ClassLoader qui cherche les classes à charger dans une liste de répertoires ou de ressources compressées (jar, zip). Pour cela vous devez créer une classe MyClassLoader qui spécialise la classe SecureClassLoader et surcharge la méthode loadClass. On pourra par exemple compléter le code donné ci-dessous:

public class MyClassLoader extends SecureClassLoader {
 private ArrayList<File> path = null;
 public MyClassLoader(ArrayList<File> p) {
   this.path = p;
 }
 @Override
 protected Class<?> findClass(String name) throws ClassNotFoundException {
   byte[] b = loadClassData(name);
   return super.defineClass(name, b, 0, b.length);
 }
 
 private byte[] loadClassData(String name) throws ClassNotFoundException {
 // TODO  A COMPLETER  
  return null;
 }

 public static void main(String[] args) {
   ArrayList<File> al = new ArrayList<File>();
   al.add(new File(....));
  MyClassLoader myCL = new MyClassLoader(al);
  try {
    myCL.loadClass(...);
  } catch (ClassNotFoundException e) {
   e.printStackTrace();
   }
  }
}

Le nom de classe donné est le nom qualifié (par exemple: miage.m1.MaClasse). Il s'agit alors de parcourir chaque chemin dans la liste path, pour chercher la présence d'un dossier miage qui contient un dossier m1 qui contient un fichier MaClasse.class. Il faut alors lire tout le contenu de ce fichier pour extraire un tableau de type byte[] (avec la méthode read() de InputStream, par exemple).

Attention ! La classe miage.m1.MaClasse doit nécessairement être définie dans une hiérarchie de fichiers différente de celle dans laquelle la classe ClasseLoader est développée (c'est-à-dire un projet différent si vous êtes sous Eclipse ou Netbeans). Le répertoire en question ne doit PAS être dans le CLASSPATH utilisé pour exécuter votre programme de test.

Pour tester votre programme il faut se souvenir qu'il ne faut pas appeler la méthode findClass directement. C'est d'ailleurs la plupart du temps impossible car elle est protected. Voir le cours. Un projet Maven est disponible sur GitHub (https://github.com/fabricehuet/testmyclassloader ). Il contient quelques tests simples pour vérifier que votre ClassLoader fonctionne. 

Seulement des répertoires

Dans un premier temps on supposera que les fichiers présents dans la liste path sont des dossiers. Utiliser la méthode isDirectory() pour s'en assurer le cas échéant.

Fichiers archives

Dans un second temps, on considérera que les fichiers présents dans la liste path peuvent également représenter des fichiers archives (.jar ou .zip). On utilisera alors les classes JarFile et ZipFile pour extraire les fichiers .class des archives.

Repository

On veut maintenant pouvoir charger toutes les classes qui se trouvent dans un répertoire donné. On utilisera pour faire le chargement, la classe MyClassLoader développée à l'exercice précédent.

Toutes les classes

Dans un premier temps, on chargera et renverra toutes les classes correspondant à un fichier .class, sans discrimination.

On pourra par exemple compléter la classe Repository ci-dessous.

package miage.m1.dynamicLoading;

public class Repository{
  public Repository(File base) {
    // TODO A compléter
  }

  public List<Class<?>> load() {
    // TODO A compléter
  }
}

Seulement les classes héritant de T

Dans un deuxième temps, on renverra uniquement les classes héritant d'un type donné

On pourra par exemple compléter la classe Repository ci-dessous.

package miage.m1.dynamicLoading;

public class Repository<T> {
  public Repository(File base, Class<T> superClass) {
    // TODO A compléter
  }

  public List<Class<? extends T>> load() {
    // TODO A compléter
  }
}

Menu

Pour tester vos classes:

  • Créer une fenêtre JFrame
  • Y ajouter une barre de menu (JMenuBar) et un menu "dynamic"
  • Ajouter dans le menu "dynamic" un sous-menu pour chaque classe de type Action que vous trouverez dans un repository à définir et à remplir par vos soins.

La classe Java ci-dessous créé une fenêtre et y ajoute un menu statiquement. A vous de la modifier pour y ajouter un menu dynamiquement selon les instructions données plus haut.

package miage.m1.menu;

import java.awt.BorderLayout;

public class FrameWithMenu {
 void showFrame() {
  if (frame == null) {
   frame = new JFrame("Test menu");
   frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   frame.setBounds(100, 100, 450, 300);
   contentPane = new JPanel();
   contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
   contentPane.setLayout(new BorderLayout(0, 0));
   frame.setContentPane(contentPane);
  }
  buildMenu();
  frame.setVisible(true);
 }

 JFrame frame;
 private JPanel contentPane;

 @SuppressWarnings("serial")
 void buildMenu() {
  JMenuBar bar = new JMenuBar();
  frame.setJMenuBar(bar);
  JMenu fileM = new JMenu("Fichier");
  bar.add(fileM);
  fileM.add(new AbstractAction("Save", new ImageIcon("res/save-icon16.png")) {
   @Override
   public void actionPerformed(ActionEvent arg0) {
    System.out.println("DoSaveAction:"+arg0);
   }
   @Override
   public Object getValue(String arg0) {
    if(arg0==Action.ACCELERATOR_KEY) // cannot be changed later (use putValue when possible - not anonymous)
     return KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK);
    return super.getValue(arg0);
   }
  });
 }

 public static void main(String[] args) {
  new FrameWithMenu().showFrame();
 }
}