2010-05-31 23 views
59

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?Comment compiler et instancier par programme une classe Java?

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(); 

Est-ce que la newInstance ne charger que les fichiers .class compilé? Comment charger une classe Java qui n'est pas compilée?

Répondre

118

Comment charger une classe Java qui n'est pas compilée?

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

Voici un exemple de coup d'envoi de base (en laissant exception évidente manipulation de côté):

// 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 "[email protected]". 

Ce qui donne comme

hello 
world 
[email protected] 

Toute autre utilisation serait plus facile si ces classes implements une certaine interface qui est déjà dans le classpath.

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

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


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

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

Laisser java.io.File comptent sur le répertoire de travail actuel est la recette pour des problèmes de portabilité. Ne fais pas ça. Placez ce fichier dans classpath et utilisez ClassLoader#getResourceAsStream() avec un chemin relatif au chemin d'accès aux classes.

properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("ClassName.properties")); 
+0

Juste une question hors sujet. J'obtiens un null quand je charge les propriétés à votre guise mais j'obtiens les propriétés quand je le fais Foo.class.getResourceAsStream()? Pourriez-vous m'aider à comprendre votre code? Je vous remercie. – unj2

+0

Ce fichier de propriétés est apparemment placé dans le même paquet que la classe 'Foo'. Comme indiqué, vous devez spécifier un chemin relatif au chemin de classe, par ex. 'com/example/nomdefichier.properties'. Mais si vous pouvez garantir que le fichier de propriétés est toujours dans le même paquet que la classe 'Foo', alors' Class # getResourceAsStream() 'est également correct. Vous ne manquerez que la possibilité d'externaliser le fichier de propriétés en dehors de l'application afin qu'il puisse être modifié sans modifier/reconditionner l'application. – BalusC

+0

'Files.write (source, sourceFile, StandardCharsets.UTF_8);' ne compile pas pour moi. Je pense que vous voulez 'Files.write (sourceFile.toPath(), source.getBytes());'. –

4

Votre code commenté est correct si vous savez que la classe a un constructeur public sans arg. Il vous suffit de lancer le résultat, car le compilateur ne peut pas savoir que la classe implémentera effectivement IDynamicLoad. Donc:

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 d'un code source, c'est tout un tas de poisson.

5

Dans la même veine que la réponse de BalusC, mais un peu plus emballage 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 la source Java, extrait le package et les noms de classes/interfaces publics et crée la hiérarchie de répertoire/fichier correspondante dans un répertoire tmp. Il exécute ensuite le compilateur java dessus, et renvoie une liste de noms, paires de classfile (la structure ClassInfo).

Aidez-vous au code. C'est une licence MIT.

+0

Que se passe-t-il si la compilation échoue? –