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 {
-
- 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();
- }
- }
- }
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) {
-
- }
-
- public List<Class<?>> load() {
-
- }
- }
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) {
-
- }
-
- public List<Class<? extends T>> load() {
-
- }
- }
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)
- return KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK);
- return super.getValue(arg0);
- }
- });
- }
-
- public static void main(String[] args) {
- new FrameWithMenu().showFrame();
- }
- }
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();
}
}