Wie kann man eine Klasse aus einer *.jar-Datei auslesen und deren Methoden ausführen?v.5.0

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:

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.

Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.