Classpath Zur Laufzeit bestimmte Klassen in Classloader hinzufügen?

lam_tr

Top Contributor
Hallo zusammen,

ich bastel gerade ein graphischer Editor mit Möglichkeit weitere "Plugins" / "Extensions" hinuzfügen zu können aufbasis von JavaFX.

Wie kann ich zur Laufzeit eine Jar als Plugin hinzufügen? Ich habe hier sowas gefunden hier: https://stackoverflow.com/questions/60764/how-to-load-jar-files-dynamically-at-runtime
Code:
URLClassLoader child = new URLClassLoader(
        new URL[] {myJar.toURI().toURL()},
        this.getClass().getClassLoader()
);
Class classToLoad = Class.forName("com.MyClass", true, child);
Method method = classToLoad.getDeclaredMethod("myMethod");
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance);

In jede selbsterstelle Plugin Jar von mir enthält eine xml Datei, wo bestimmte Ansichten gespeichert sind. E.g. plugin1.jar -> plugin.xml
Code:
</plugin>
  <view id="mycustomview" uri="de.test.project.MyCustomView"/>
</plugin>

Wie kann ich dann die plugin.xml laden? Wenn ich 10 plugins habe, dann würde ich auch 10 plugin.xml haben. Jedes mal wenn ich ein "Add Plugin to App" mache , soll doch irgendwo die Abhängigkeit hinzugefügt werden oder? Die plugin.xml einlesen würde danach doch gehen oder getClass().getResource("plugin.xml")?

Relativ zur Anwendung habe ich ein Plugin Order, wo solche abgelegt werden sollten. Wie kann ich meine Anwendung mit solche Plugins laden, oder in die Abhängigkeit/ Classloader ergänzen?

Ich schätze das ist ein paar Sachen durcheinander geraten.

Vielleicht fangen wir mit der ersten Frage an. Wie kann ich die jar Datei zu meiner Anwendung in die Abhängigkeit hinzufügen? Als Eclipse Plugin hätte ich die manifest Datei gesagt. Aber sowas dynamisches ist schwierig zu sagen. Ich könnte auch eine XML Datei mit Jars auflisten, aber dann müsste ich auch wissen wie ich dynamisch inkludieren soll?

So was ähnliches kann ich doch aufsetzen oder plugin-dependencies.xml?
Code:
<dependencies>
   <plugin>de.test.plugin1.jar</plugin>
   <plugin>de.test.plugin2.jar</plugin> 
   <plugin>de.test.plugin3.jar</plugin>
   <plugin>de.test.plugin4.jar</plugin>
</dependencies>
Grüße
lam
 
Dein genaues Vorhaben / Problem ist mir noch nicht ganz klar:

Wenn die Applikation dynamisch Erweiterungen laden können soll, dann stellt sich erst einmal die Frage nach dem Classloader: Für welche Funktionalität benötigst Du ein dynamisches Laden über einen selbst erstellten Classloader? Reicht es nicht schon aus, wenn man einfach die Applikation so startet, dass alle jar Dateien eines Verzeichnisses im ClassPath enthalten sind? => Beim Start wären dann alle Klassen verfügbar.
Das wäre also sowas wie ./plugins/* innerhalb des classpaths.

Oder benötigst Du irgendwelche erweiterten Funktionen a.la. hinzufügen / entfernen / aktualisieren von Abhängigkeiten, so dass da kein Neustart der Applikation in Frage kommt?

Das wäre dann ein erster, einfacher Start, um eben auf Klassen zur Laufzeit zugreifen zu können.

Dann bleibt die Frage nach der Konfiguration: Da kannst Du natürlich alles nutzen, was es gibt: Angefangen von einfachen properties-Dateien über entsprechende XML Dateien. Diese liest du dann mit den entsprechenden Mitteln ein und wertest die Daten dann aus. So eine XML Datei könnte einfach etwas serialisiertes sein.
Und was da benötigt wird, sind in erster Linie z.B. ein Klassenname, von dem dann eine Instanz erzeugt wird und die dann ein gewisses Interface implementiert. (Das wäre so mein Ansatz).

Aber wenn Du etwas ausführlicher schreibst, was Du genau möchtest, dann kann man ggf. konkretere Hilfe geben.
 
Dein genaues Vorhaben / Problem ist mir noch nicht ganz klar:

Wenn die Applikation dynamisch Erweiterungen laden können soll, dann stellt sich erst einmal die Frage nach dem Classloader: Für welche Funktionalität benötigst Du ein dynamisches Laden über einen selbst erstellten Classloader? Reicht es nicht schon aus, wenn man einfach die Applikation so startet, dass alle jar Dateien eines Verzeichnisses im ClassPath enthalten sind? => Beim Start wären dann alle Klassen verfügbar.
Das wäre also sowas wie ./plugins/* innerhalb des classpaths.

Oder benötigst Du irgendwelche erweiterten Funktionen a.la. hinzufügen / entfernen / aktualisieren von Abhängigkeiten, so dass da kein Neustart der Applikation in Frage kommt?

Das wäre dann ein erster, einfacher Start, um eben auf Klassen zur Laufzeit zugreifen zu können.

Dann bleibt die Frage nach der Konfiguration: Da kannst Du natürlich alles nutzen, was es gibt: Angefangen von einfachen properties-Dateien über entsprechende XML Dateien. Diese liest du dann mit den entsprechenden Mitteln ein und wertest die Daten dann aus. So eine XML Datei könnte einfach etwas serialisiertes sein.
Und was da benötigt wird, sind in erster Linie z.B. ein Klassenname, von dem dann eine Instanz erzeugt wird und die dann ein gewisses Interface implementiert. (Das wäre so mein Ansatz).

Aber wenn Du etwas ausführlicher schreibst, was Du genau möchtest, dann kann man ggf. konkretere Hilfe geben.

Also es geht mir hier darum einfach ein generischen Editor zu entwickeln der Plugins für Shapes, für Diagramme, etc. als Erweiterung anzubieten. Nicht jeder will von vorne herein alles haben. Deswegen will ich nur dieser Editor Workbench als Gerüst als Start verpacken und die anderen Plugins als zusätzliche Pakete, die man im Laufe der Zeit in das Gerüst als Benutzer nachinstallieren kann.

Auch dem Benutzer ist überlassen, eigene Plugins zu schreiben. Somit kann er auch selbst sein Plugin in das Workbench reinladen.

Wenn die Applikation dynamisch Erweiterungen laden können soll, dann stellt sich erst einmal die Frage nach dem Classloader: Für welche Funktionalität benötigst Du ein dynamisches Laden über einen selbst erstellten Classloader? Reicht es nicht schon aus, wenn man einfach die Applikation so startet, dass alle jar Dateien eines Verzeichnisses im ClassPath enthalten sind? => Beim Start wären dann alle Klassen verfügbar.
Das wäre also sowas wie ./plugins/* innerhalb des classpaths.

Zu deiner ersten Frage: Ich weiß zu dem Start der Anwendung nicht wieviele plugin jars noch nachkommen. Aber das heißt auch, jedes mal wenn ich eine neue Plugin implementiere, dann muss ich auch das Projekt in meine Applikation inkludieren. Was ist wenn ich nur plugins entwickeln kann aber keinen Zugriff auf die Applikation?

Oder benötigst Du irgendwelche erweiterten Funktionen a.la. hinzufügen / entfernen / aktualisieren von Abhängigkeiten, so dass da kein Neustart der Applikation in Frage kommt?
Wenn ich ein plugin hinzufüge und zur Laufzeit wird es gezogen ohne Neuzustarten wäre optimal, aber ich sag mal ich bin auch bereit neuzustarten wenn die Abhängigkeiten gezogen werden.

Ich versuche schon das einlesen der Jar zu realisieren, komme leider nicht an die plugin.xml ran. Wie kann ich von InputStream zur File("test.resource") kommen? Das wäre jetzt ein Beispiel dieser Seite https://stackoverflow.com/questions/5957589/load-resource-from-jar-file-at-runtime
Code:
JarFile jarFile = new JarFile(new File("/out/resource.jar"))
JarEntry jarEntry = jarFile.getJarEntry("test.resource")
InputStream inputStream = jarFile.getInputStream(jarEntry)

An sich will ich sowas wie Eclipse nachbauen aber ohne OSGI.
 
Also nur als Beispiel: Schau Dir JDBC an!

Da hast Du eine Library, die gewisse Dinge implementiert. Du kannst beliebig viele JDBC Treiber im Klassenpfad haben. Generell muss die Applikation noch nicht einmal wissen, was für eine JDBC Library verwendet wird - der connection string ist dann halt unterschiedlich....

Und mal angenommen, du wolltest einen JDBC Treiber selbst entwickeln: Du brauchst natürlich keinen Zugriff auf die späteren Applikationen, die diesen verwenden wollen. Aber natürlich brauchst Du Zugriff auf gewisse Dinge, die Du implementieren sollst.

So macht es durchaus Sinn, dass Du Dinge, die als Grundlage dienen und auch von einem Plugin benötigt werden (also Interfaces, Basisklassen, ...) in einer separaten Library bereit stellst. Dann hast eine klarere Abhängigkeiten...

Und die jar so als Archiv einlesen? Wenn, dann würde ich es wirklich über den ClassLoader machen - also Dein erster Ansatz. Denn Du willst ja auch Klassen nutzen und so ... Aber da wäre meine Frage immer: Wozu? Eine JDBC Library willst Du so ja auch nicht einbinden ... Die wird einfach in den Pfad kopiert und dann steht sie zur Verfügung. Damals noch das Erzeugen einer Klasse über Name, aber das entfällt ja seid JDBC 4.0 auch auf Grund der ServiceLoader Funktionalität (https://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html).

Wäre das nicht so viel schöner? Die AddOns kommen einfach in ein Verzeichnis, das beim Start im ClassPath ist. Durch einen ServiceLoader Eintrag können die sich selbst registrieren. Du kannst dann in der Applikation ja dann eine normale Verwaltung haben so von wegen aktiviertes AddOn oder Deaktiviertes AddOn.

Etwas in der Art wäre halt mein erster Ansatz bezüglich eines AddOns. Und da irgendwelche Daten aus der JAR Datei ziehen? Das würde ich dem Entwickler des AddOns überlassen. Wenn irgendwelche Informationen z.B. über das AddOn benötigt würden, dann wäre das im Interface z.B. vom AddOn enthalten. Also wenn Du für jedes AddOn wissen willst: Kann das AddOn den Rechner zerstören, dann gibt es halt ein Interface AddOn und das hat die Methode boolean canDestroyComputer(); Bei der Registrierung muss das AddOn halt eine Instanz von AddOn hinterlegen und die würdest Du dann nutzen ...

Waren diese Überlegungen halbwegs verständlich und vielleicht etwas hilfreich?
 
Klasse erklärt danke, ich glaube das mit dem ServiceLoader ist was ich benötige.

Ich habe zwar jetzt alles über ClassLoader gemacht, aber ServiceLoader sollte etwas sauberer sein oder?

Code:
public class PluginService implements IPluginService {

    private File folder = new File("plugins");

    public PluginService() {
        if (!folder.exists()) {
            folder.mkdir();
        }
    }

    @Override
    public WorkbenchModul loadPlugin(File jar) {
        try {
            URL url = jar.toURI().toURL();
            URL[] urls = new URL[] { url };
            URLClassLoader classLoader = new URLClassLoader(urls, ClassLoader.getSystemClassLoader());
            Thread.currentThread().setContextClassLoader(classLoader);

            JarFile jarFile = new JarFile(jar);
            JarEntry jarEntry = jarFile.getJarEntry("resources/base.modul");
            InputStream inputStream = jarFile.getInputStream(jarEntry);

            org.eclipse.emf.ecore.resource.Resource res = null;
            res = new XMIResourceImpl();

            HashMap options = new HashMap();
            options.put(XMIResource.OPTION_DECLARE_XML, Boolean.TRUE);
            try {
                res.load(inputStream, options);

                WorkbenchModul model = (WorkbenchModul) res.getContents().get(0);
                return model;
            } catch (IOException e) {
                e.printStackTrace();
            }

            jarFile.close();
        } catch (Exception ex) {
            throw new RuntimeException(
                    "Cannot load library from jar file '" + jar.getAbsolutePath() + "'. Reason: " + ex.getMessage());
        }
        return null;
    }

    @Override
    public List<String> getPluginNames() {
        return Arrays.asList(folder.list());
    }

    @Override
    public List<File> getPlugins() {
        return Arrays.asList(folder.listFiles());
    }

    @Override
    public void addPluginToClassPath(File jar) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, MalformedURLException {
        // Get the ClassLoader class
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Class<?> clazz = cl.getClass();
      
        // Get the protected addURL method from the parent URLClassLoader class
        Method method = clazz.getSuperclass().getDeclaredMethod("addURL", new Class[] {URL.class});
      
        // Run projected addURL method to add JAR to classpath
        method.setAccessible(true);
        method.invoke(cl, new Object[] {jar.toURI().toURL()});
    }

    @Override
    public List<WorkbenchModul> loadPlugins() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, MalformedURLException {
        List<WorkbenchModul> modules = new ArrayList<>();
        for (File plugin : getPlugins()) {
            addPluginToClassPath(plugin);
            modules.add(loadPlugin(plugin));
        }
        return modules;
    }

}
 

Neue Themen


Zurück
Oben