# Realisierung einer "Plugin-Schnittstelle":



## Ulathar (2. Aug 2011)

Hallo,
ich habe da auch mal wieder eine Frage. Und zwar Frage ich mich und hiermit euch, wie man am sinnvollsten vorgehen sollte, wenn man einem Programm die Möglichkeit geben will, dass andere Entwickler für dieses Programm Plugins schreiben können.
Am besten ohne direkten Zugang zum eigendlichen Sourcecode zu haben.

Ein simples Beispiel was mir da so in etwa vorschwebt:

Ich habe ein GUI, das in seiner Menubar irgendwo das MenuItem "Plugins" enthält.
Dieses MenuItem Plugins soll nun sämtliche "Plugins" enthalten, die in form eines z.b. JARs in dem ordner /plugins/ liegen.

Bei einem Klick auf das jeweilige Plugin soll dann logischer weise das JAR ausgeführt werden, am besten mit gewissen übergabeparametern/daten aus dem eigendlichen GUI und dann auf die "Antwort" des Plugins gewartet werden, also am Ende des Plugins sollen ver/bearbeitete Daten an das GUI zurückgeliefert werden.

Hoffe es ist klar was ich in etwa meine.
Mein erster Ansatz wäre dazu bei Programmstart (ehe das GUI gebaut wird) den Ordner zu scannen und für jedes dort gefundene JAR ein MenuItem zu erzeugen.
Anschließend würde ich bei einem Klick auf das Item das jeweilige JAR executen lassen. Nur wie kann ich an dieser Stelle dann z.B. auf die Rückgabe warten? (das können ettliche hundert MB an daten werden theoretisch!).

Falls der Ansatz totaler Mist ist und es dafür bessere Bordmittel oder ähnliches gibt, bin ich für jeden Hinweis natürlich dankbar .


----------



## Tomate_Salat (2. Aug 2011)

Ich würde hierfür mind. OSGi und am besten gleich RCP verwenden. Bei letzterem kannst du Plugins schreiben, die genau die Möglichkeiten bieten, wie du sie von Eclipse kennst.


----------



## nillehammer (2. Aug 2011)

Hallo,
Du definierst drei Regeln, an die sich Deine Plugin Entwickler halten sollen:
1. Sie müssen ein Interface implementieren, dass Du definierst. In diesem spezifizierst Du, was Du an Funktionalität von Plugins erwartest.
2. Sie sollen Dir ihre Plugins als jar ausliefern. In dieser jar gibt es die Datei "META-INF/MANIFEST.MF" ist Standard bei jars. In dieser Datei sollen sie einen Eintrag machen, der den voll qualifizierten Klassennamen ihrer implementierenden Klasse spezifiziert.
3. Du sagst den Plugin Entwicklern, dass diese Klasse einen public no argument Konstruktor haben muss.

Wenn die Plugin Entwickler sich an die Regeln halten und Dir ihre jars geschickt haben, pacskt Du sie mit zu den libs Deiner Anwendung. Beim Start der Anwendung machst Du dann folgendes:
1. Du holst Dir die URLs aller MANIFEST.MF (siehe hierzu di Methode getResources von ClassLoader: ClassLoader (Java Platform SE 6)) )
2. Du machst aus jeder gefundenen MANIFEST.MF ein Manifest Objekt (Manifest (Java Platform SE 6)) Mit diesem kannst Du dann nach dem in Regel 2 spezifizierten Klassennamen schauen und ihn Dir z.B. in einer Liste merken, wenn er vorhanden ist.
3. Du gehst die Liste mit den Klassennamen durch und lädst Dir die Klasse mit: Class.forName(...) (Class (Java Platform SE 6))
4. Mit der geladenen Klasse erzeugst Du die Instanz der Plugin Implementierung mitttels der Methoden Class.getConstructor().newInstance();


----------



## planetenkiller (2. Aug 2011)

nillehammer hat gesagt.:


> Hallo,
> Du definierst drei Regeln, an die sich Deine Plugin Entwickler halten sollen:
> 1. Sie müssen ein Interface implementieren, dass Du definierst. In diesem spezifizierst Du, was Du an Funktionalität von Plugins erwartest.
> 2. Sie sollen Dir ihre Plugins als jar ausliefern. In dieser jar gibt es die Datei "META-INF/MANIFEST.MF" ist Standard bei jars. In dieser Datei sollen sie einen Eintrag machen, der den voll qualifizierten Klassennamen ihrer implementierenden Klasse spezifiziert.
> ...



Geht mit ServiceLoader (Java Platform SE 6) viel einfacher.
Die Jars können zur Laufzeit mit URLClassLoader (Java Platform SE 6) geladen werden.


----------



## Landei (2. Aug 2011)

Die einfachste Variante ist ein Service Provider Interface. Du definierst ein Interface, und jedes jar, das dazu passende Implementierungen enthält, kann diese in seinem META_INF/services Verzeichnis angeben. Deine Anwendungen kann sich dann alle diese Implementierungen, die im Classpath liegen, automatisch auflisten lassen. Siehe z.B. Creating Extensible Applications With the Java Platform oder Service Provider Interface: Creating Extensible Java Applications - Developer.com


----------



## Ulathar (2. Aug 2011)

Danke euch erstmal für die Denkanstöße, ich denke ich werde mir das mit den Services näher ansehen, da das am einfachsten/schnellsten zu realisieren zu sein scheint (habe nur wenig Zeit mein Programm um diese Funktionalität zu erweitern, daher muss es was Quick & Dirty für den Anfang sein ^^).

Das Problem ist, dass die potentiellen Plugin-Entwickler ihre JARs nicht an mich schicken werden (wie oben vorgeschlagen), sondern eher ich mein Programm "ausliefer" und die die damit arbeiten dann weiß der Geier was damit anstellen werden (in Bezug auf Plugins).

Ich nehme an, über das Service Provider Interface regel ich dann auch die Datenübermittlung vom Hauptprogramm zum Plugin zurück zum Hauptprogramm?
Falls jemand noch Codeschnipsel rumliegen hat nehm ich die auch gern, wird aber auch so gehen, sind ja ein paar Beispiele auf den oben verlinkten Seiten! 

Bin dann mal lesen, ist n völlig neues Kapitel für mich .


----------



## nillehammer (2. Aug 2011)

> Geht mit ServiceLoader (Java Platform SE 6) viel einfacher.
> Die Jars können zur Laufzeit mit URLClassLoader (Java Platform SE 6) geladen werden.


Stimmt, ich konnte mich nur nicht mehr erinnern, wie der Mechanismus heißt und konnte deswegen bei google keinen vernünftigen Suchbegriff eingeben, um den Link zu finden. Danke für's Posten, jetzt finde ich ihn immer wieder:


----------



## TheDarkRose (2. Aug 2011)

Ulathar hat gesagt.:


> Das Problem ist, dass die potentiellen Plugin-Entwickler ihre JARs nicht an mich schicken werden (wie oben vorgeschlagen), sondern eher ich mein Programm "ausliefer" und die die damit arbeiten dann weiß der Geier was damit anstellen werden (in Bezug auf Plugins).


Um Tomate_Salat zu zitieren.


Tomate_Salat hat gesagt.:


> Ich würde hierfür mind. OSGi und am besten gleich RCP verwenden. Bei letzterem kannst du Plugins schreiben, die genau die Möglichkeiten bieten, wie du sie von Eclipse kennst.


Eclipse RCP ist für sowas de facto schon fast "Industriestandard"  Damit kenne sich relativ viele Entwickler aus, ist ausgereift und getestet und fremde Entwickler müssten sich nicht in irgendwelche Eigenimplementierungen einarbeiten. Und du ersparst dir auch jede Menge Zeit.


----------



## Ulathar (9. Sep 2011)

Ich muss das Thema leider doch noch mal aufgreifen.
Ich hatte endlich die Zeit mich etwas intensiver damit zu befassen und habe nun für den Anfang einmal versucht das Beispiel aus Creating Extensible Applications With the Java Platform , bzw Service Provider Interface: Creating Extensible Java Applications - Developer.com  in mein bereits bestehendes Projekt zu integrieren.
Ich bin mir auch (fast) sicher, dass der Code an sich stimmt, aber die Plugins funktionieren dennoch nicht und ich find nicht den Grund dafür.

Meine Struktur:

Hauptprogramm besteht aus 17 Packages, relevant wären die folgenden 3 Packages:
main
spi
plugin

im package main liegt die Main-Class, in spi liegt das Interface (Dictionary im Beispiel) und in plugin liegt die Implementierung des Interface ServiceProvider (GeneralDictionary im Beispiel).

In der MainClass führe ich folgenden Test der "Plugins" durch:

```
PluginService plug = PluginService.getInstance();
System.out.println("Test1 mit Test:  " + plug.getDefinition("Test"));
System.out.println("Test2 mit book:  " + plug.getDefinition("book"));
System.out.println("Test3 mit XML:   " + plug.getDefinition("XML"));
```

Also ebenfalls analog zu dem Beispiel. Ich erhalte als Rückgabe aber immer "null", was höchst wahrscheinlich daran liegt, dass mein Programm die 2 Plugins nicht korrekt erkennt.

Hier mal eines der Plugins:


```
package plugin;

import java.util.SortedMap;
import java.util.TreeMap;

import spi.Plugin;


public class ExamplePlugin implements Plugin {	

    private SortedMap<String, String> map;

    /** Creates a new instance of GeneralDictionary */
    public ExamplePlugin() {
        map = new TreeMap<String, String>();
        map.put("book", "a set of written or printed pages, usually bound with " +
                "a protective cover");
        map.put("editor", "a person who edits");
    }

    
    public String getDefinition(String word) {
        return map.get(word);
    }
}
```


Jedes Plugin ist dabei ein eigenes Projekt, hoffe das ist richtig so... Damit der Code compiliert muss ich allerdings dem Projekt mein Hauptprojekt (für das das Plugin sein soll) in form eines external JARS mit in den Buildpath aufnehmen (da er logischerweise sonst das Plugin-Interface nicht kennt).

Das Plugin verpacke ich dann wie im Beispiel angegeben in ein Jar. Das JAR sieht dann folgendermaßen aus:

[plugin] -> ExamplePlugin.class
[META-INF] -> [services] -> spi.Plugin

in der spi.Plugin Datei steht folgendes:
plugin.ExamplePlugin


Das müsste doch so alles passen oder nicht? Ich blick einfach nicht wo der Fehler ist .
Das exportierte Plugin-JAR habe ich natürlich in enen Unterordner meines Hauptprojekts kopiert, der im CLASSPATH liegen müsste.

Kann bei Bedarf auch noch weiteren Code (oder auch die Sources selbst) anbieten, aber da ich bis auf einige Namen quasi 1:1 die Struktur der beiden Beispiele beibehalten habe könnte das auch da eingesehen werden.


Gibt es alternativ noch andere Lösungen mit denen ich Ziel erreichen könnte? (es genügt, wenn ich ein JAR aus meinem Programm heraus mit gewissen Übergabeparatern, z.b. einem String[], starten könnte und dann auf eine Antwort, wieder ein String[], des externen JARS warte, weiß aber nicht ob das mit JARs überhaupt so möglich ist).


----------



## Wildcard (9. Sep 2011)

> Das exportierte Plugin-JAR habe ich natürlich in enen Unterordner meines Hauptprojekts kopiert, der im CLASSPATH liegen müsste.


Wenn ein Verzeichnis im Classpath liegt, heißt das noch nicht das die jars in diesem Verzeichnis auch im Classpath liegen. Insbesondere aus der IDE heraus solltest du die jars im Verzeichnis explizit in den Classpath aufnehmen.


> Gibt es alternativ noch andere Lösungen mit denen ich Ziel erreichen könnte?


Hier wurde doch schon erwähnt das OSGi/Eclipse RCP die Standardlösung für diese Art Problem ist.


----------



## Ulathar (9. Sep 2011)

Leider is OSGi/RCP für mich zum jetzigen Zeitpunkt keine Option weil die Zeit die ich benötigen würde um mich in RCP einzuarbeiten und dann mein Projekt darauf zu portieren einfach nicht da ist (habe über 60 Klassen mittlerweile). Wenn ich das so überfliege (z.B. Eclipse RCP Tutorial) scheint das leider nicht ansatzweise mit meinem bisherigen Projekt kompatibel zu sein .

Was das hinzufügen der Jars betrifft:
habe es gerade aus Eclipse heraus probiert und es scheint zu funktionieren, aber es ist problematisch,  wenn ich für jedes Plugin das irgendwer geschrieben hat das Projekt neu builden müsste (die Anwender starten das Tool nicht über Eclipse sondern über einen mit launch4j erzeugten launcher)...

Danke jedenfalls für den Hinweis mit dem expliziten hinzufügen. Nun weiß ich wenigstens, dass der Code an sich korrekt ist.
Jetzt muss ich "nur" noch eine Lösung dafür finden, wie ich die JARs automatisch adden kann ohne jedes mal das Programm neu builden zu müssen (das ist deployment technisch nicht möglich).

Es muss doch möglich sein, alle JARs aus einem ordner "automatisch" dem classpath hinzuzufügen...

Die Struktur ist momentan folgende:

.../programm/Launcher.exe
.../programm/bin/hauptjar  (wird vom Launcher gestartet)
.../programm/lib/jdbctreiber, jogl, etc liegt hier
.../programm/res/hier sind dinge wie icons, sound, etc
.../programm/plugins/alle plugin jars


----------



## TheDarkRose (9. Sep 2011)

Damit war ja nicht gemeint das du alles auf Eclipse RCP umstellen solltest. OSGI ist auch ohne Eclipse RCP lauffähig und eigentlich Standard bei Pluginanwendungen. So schwer dürfte es nicht sein, auf deinen Klassen OSGI Bundles zu machen.


----------



## Wildcard (9. Sep 2011)

> habe es gerade aus Eclipse heraus probiert und es scheint zu funktionieren, aber es ist problematisch, wenn ich für jedes Plugin das irgendwer geschrieben hat das Projekt neu builden müsste (die Anwender starten das Tool nicht über Eclipse sondern über einen mit launch4j erzeugten launcher)...


Mir ist schon klar das die Anwender das nicht über Eclipse machen, aber *du* musst das in Eclipse so machen.
Fürs Deployment ist ein üblicher Weg ein 'lib' oder 'plugins' Folder und ein Startscript das den Classpath passend setzt.


----------

