web-dev-qa-db-fra.com

Comment compiler et instancier par programmation une classe Java?

J'ai le nom de classe stocké dans un fichier de propriétés. Je sais que le magasin de classes implémentera IDynamicLoad. Comment instancier la classe dynamiquement?

En ce moment j'ai

     Properties foo = new Properties();
    foo.load(new FileInputStream(new File("ClassName.properties")));
    String class_name = foo.getProperty("class","DefaultClass");
    //IDynamicLoad newClass = Class.forName(class_name).newInstance();

Le newInstance charge-t-il uniquement les fichiers .class compilés? Comment charger une classe Java non compilée?

64
unj2

Comment charger une classe Java classe non compilée?

Vous devez d'abord le compiler. Cela peut être fait par programme avec l'API javax.tools . Cela nécessite uniquement l'installation de JDK sur la machine locale au-dessus de JRE.

Voici un exemple de démarrage (en laissant de côté la gestion des exceptions évidentes):

// Prepare source somehow.
String source = "package test; public class Test { static { System.out.println(\"hello\"); } public Test() { System.out.println(\"world\"); } }";

// Save source in .Java file.
File root = new File("/Java"); // On Windows running on C:\, this is C:\Java.
File sourceFile = new File(root, "test/Test.Java");
sourceFile.getParentFile().mkdirs();
Files.write(sourceFile.toPath(), source.getBytes(StandardCharsets.UTF_8));

// Compile source file.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, sourceFile.getPath());

// Load and instantiate compiled class.
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { root.toURI().toURL() });
Class<?> cls = Class.forName("test.Test", true, classLoader); // Should print "hello".
Object instance = cls.newInstance(); // Should print "world".
System.out.println(instance); // Should print "test.Test@hashcode".

Qui donne comme

hello
world
test.Test@ab853b

Une utilisation ultérieure serait plus facile si ces classes implements une certaine interface qui était déjà dans le chemin de classe.

SomeInterface instance = (SomeInterface) cls.newInstance();

Sinon, vous devez impliquer Reflection API pour accéder et appeler les méthodes/champs (inconnus).


Cela dit et sans rapport avec le problème réel:

properties.load(new FileInputStream(new File("ClassName.properties")));

Laisser Java.io.File S'appuyer sur le répertoire de travail actuel est une recette pour des problèmes de portabilité. Ne fais pas ça. Mettez ce fichier dans classpath et utilisez ClassLoader#getResourceAsStream() avec un chemin relatif à classpath.

properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("ClassName.properties"));
132
BalusC

Dans la même veine que la réponse de BalusC, mais un wrapper un peu plus automatique est ici dans ce morceau de code de ma distribution kilim. https://github.com/kilim/kilim/blob/master/src/kilim/tools/Javac.Java

Il prend une liste de chaînes contenant Java, extrait le package et les noms de classe/interface publique et crée la hiérarchie de répertoire/fichier correspondante dans un répertoire tmp. Il exécute ensuite le Java dessus, et retourne une liste de paires nom, classfile (la structure ClassInfo).

Aidez-vous au code. Il est MIT sous licence.

6

Votre code commenté est correct si vous savez que la classe a un constructeur public sans argument. Il suffit de transtyper le résultat, car le compilateur ne peut pas savoir que la classe implémentera en fait IDynamicLoad. Alors:

   IDynamicLoad newClass = (IDynamicLoad) Class.forName(class_name).newInstance();

Bien sûr, la classe doit être compilée et sur le chemin de classe pour que cela fonctionne.

Si vous cherchez à compiler dynamiquement une classe à partir du code source, c'est une toute autre marmite de poisson.

5
Yishai