Dies lässt sich mit Hilfe der Reflection-API
lösen. Sie stellt Zugriffsmethoden bereit, um innerhalb des Klassenpfades
dynamisch Klassen zu laden und auszuführen
Gehen wir von einer Klasse Test.java aus, die
kompiliert und als Test.jar gepackt wird. Denken wir weiterhin, dass Test.java
im package jar liegt, sodass der komplette Pfad im Klassenpfad jar/Test.java lautet. Die Klasse enthält einige
Methoden die sich teilweise gegenseitig aufrufen und lediglich Strings zur
Dokumentation ausgeben.
public class Test {
public Test() {
System.out.println("Test aufgerufen");
init();
}
public void init() {
System.out.println("Test.init() aufgerufen");
}
public void init(String s, int i) {
System.out.println(s + i);
double d = (double) i;
gibAus(d);
init();
}
public double gibAus(double d) {
System.out.println("Test.gibAus() aufgerufen mit Parameter " + d);
return d;
}
public static void main(String[] args) {
new Test();
}
}
Die Klasse JarAuslesen ermittelt die gesuchte Klasse
Test
aus der jar-Datei und führt deren Methoden aus.
In
main() werden zwei Methoden aufgerufen:
- readClassFromJar() gibt die in der *.jar ermittelte Klasse als
class-Objekt zurück. Die Ausgabe wird auf der Konsole ausgegeben.
- loadMethods() ruft die in Test.java deklarierten Methoden
auf.
Um die Klasse zu ermitteln wird in der Methode
readClassFromJar() der
ClassLoader der aktuellen Klasse geladen. Er sucht die gewünschte jar-Datei
über ein URL-Objekt im ClassPath. Die URL wird verwendet um ein Fileobjekt
zu bilden, mit dem die Instanz eines Jarfiles erzeugt wird.
Schließlich werden das Manifest ausgelesen und über ein
Attributs-Objekt der Name der main-Klasse ermittelt. Im Manifest wird der Pfad
der gesuchten Datei mit '/' notiert. Die Slashes müssen gegen
Punkte getauscht werden um der Package-Syntax zu entsprechen. Nun kann die
Klasse mit Hilfe des ClassLoaders geladen werden. Das ermittelte Class-Objekt
kann schließlich von der Methode zurück gegeben werden.
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
public class JarAuslesen {
@SuppressWarnings("unused")
private InputStream readStreamFromJar(String pkgname, String fname) {
String res = "/" + pkgname.replace('.', '/') + "/" + fname;
Class<?> clazz = readClassFromJar(pkgname, fname);
return clazz.getResourceAsStream(res);
}
private Class<?> readClassFromJar(String pkgname, String fname) {
Class<?> clazz = null;
ClassLoader cl = getClass().getClassLoader();
// hier wird die *.jar ermittelt
URL url = cl.getResource(pkgname + "/" + fname);
// Manifest ermitteln
File f = new File(url.getFile());
JarFile jf;
Manifest manifest = null;
try {
jf = new JarFile(f);
manifest = jf.getManifest();
} catch (IOException e) {
System.err.println("Ein-Ausgabe-Fehler bei Manifestermittlung.");
}
// Mainklasse ermitteln
String mc = null;
if (manifest != null) {
Attributes att = manifest.getMainAttributes();
mc = att.getValue(Attributes.Name.MAIN_CLASS);
}
try {
// hier muss der Packagename (mit '.') innerhalb der *.jar
// ohne Dateiendung angegeben werden
mc = mc.replace('/', '.');
clazz = cl.loadClass(mc);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return clazz;
}
private void loadMethods(String name) throws Exception {
Class<?> c;
c = Class.forName(name);
Object o = c.newInstance();
Method m[] = c.getDeclaredMethods();
for (int i = 0; i < m.length; i++) {
System.out.println("\nMethode: " + m[i].toString());
System.out.println("Methodenname: " + m[i].getName());
System.out.println("Return-Typ: " + m[i].getGenericReturnType());
try {
Type[] pType = m[i].getGenericParameterTypes();
for (Type type : pType) {
System.out.println("Parameter-Typ: " + type.toString());
if (type.equals(double.class))
m[i].invoke(o, Math.PI);
if (type.equals(int.class))
m[i].invoke(o, 4711);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
String path = "jar";
String file = "Test.jar";
JarAuslesen ja = new JarAuslesen();
System.out.println(ja.readClassFromJar(path, file));
ja.loadMethods(ja.readClassFromJar(path, file).getName());
}
}
Um die Methoden auszuführen, lädt die Methode loadMethods()
die ermittelte Klasse und erzeugt ein Objekt des ermittelten Typs. Die Methode getDeclaredMethods()
der Klasse Class erzeugt ein Method-Array, dessen Einträge in einer
Schleife ausgelesen werden. Ermittelt werden der Name, der Rückgabetyp und
- in einer weiteren Schleife - die Parametertypen. Sie werden jeweils in einem
Array abgelegt, dessen Länge abgefragt wird. Sind zwei Parameter vorhanden,
werden die ermittelten Parameter mit Werten belegt und die hier zugehörige
Methode init(String s, int i) ausgeführt.
Dies geschieht über Method.invoke(). Method.invoke() kann
eine variable Parameterzahl entgegennehmen: Der erste repräsentiert das
Objekt, auf dem die Methode ausgeführt werden soll, die folgenden die Werte
der jeweiligen Parameter. Hier wird ein String und ein Integer übergeben,
wobei letzterer durch Autoboxing aus der Variablen int zahl gecastet
wird. Auf diese Weise können auch primitive Datentypen übergeben
werden.