# Das Konzept von Plugins



## Unicate (20. April 2010)

Hi alle zusammen!

Ich habe ein Programm, welches ich gern per Plugin erweiterbar machen möchte. (ala Firefox,Eclipse oder so ähnlich)
Wie setze ich sowas um?

Weiß jemand wie man's macht oder hat jemand vllt. nen Link?

Danke schonnema!


----------



## Kai008 (20. April 2010)

Geht ganz einfach. Ich definiere zuerst ein Interface oder eine abstrakte Klasse. Erzeuge ein neues Projekt, füge eine Klasse ein die davon erbt oder es implemententiert (um Parameter übergeben zu können).
In Eclipse lege ich das Hauptprojekt als Projektverknüpfung fest, damit ich auf die Klasse/Interface zugreifen kann. Dann mache ich aus dem Plugin eine jar, lade ichs einfach per URLClassLoader von der HDD im Hauptprojekt und rufe die geerbte/implementierte Klasse davon auf.


----------



## Artorius (20. April 2010)

Hi!
Hier findet man auche in kleiens Beispielprojekt: http://www.java-blog-buch.de/d-plugin-entwicklung-in-java/

Es gibt aber auch Frameworks, die es einem erleichtern, Plugin-fähige Anwendungen zu erstellen:
http://jpf.sourceforge.net/
Grüße!


----------



## Unicate (20. April 2010)

OK, ich danke Dir!

Hier Beispielcode für alle die den Thread finden 

Zuerst das Interface im Hauptprojekt:

```
package de.unicate.playground;

public interface IPlugin {
	
	public String getName();
}
```
Jetzt das Hauptprojekt, wo das Plugin geladen werden soll:

```
package de.unicate.playground;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;


public class Playground {

	private IPlugin plugin;	
	private Playground() {
		plugin = null;
	}	
	public static void main(String[] args) throws Exception{
		Playground main = new Playground();
		main.init();
		main.start();
	}
	private void start() {
		if(null != plugin) {
			System.out.println(plugin.getName());
		} else {
			System.err.println("Pluginerror");
		}
	}
	private void init() throws Exception {
		URL pluginFile = null;
		File file = new File("TestPlugin.jar");
		if(!file.exists())
			return;
		pluginFile = new URL("file", "localhost", file.getAbsolutePath());
		URL[] urls = {pluginFile};
		URLClassLoader loader = new URLClassLoader(urls,Thread.currentThread().getContextClassLoader());
		plugin =  (IPlugin)loader.loadClass("de.unicate.playground.plugin.PlaygroundPlugin").newInstance();
	
	}		
}
```

Und zu guter letzt noch das Plugin, was ich in einem eigenen Projekt erstellt habe und das Hauptprojekt verknüpft habe, damit das Interface auffindbar ist:

```
package de.unicate.playground.plugin;

import de.unicate.playground.IPlugin;

public class PlaygroundPlugin implements IPlugin{

	public PlaygroundPlugin() {
		
	}
	@Override
	public String getName() {
		return  "Ich bin ein Plugin";
		
	}

}
```

Relativ einfach und funktioniert!


Danke nochmal an Kai008!


----------



## Kai008 (20. April 2010)

Du kannst eine File auch mit toURI.toURL (oder toURL, aber veraltet, weil keine Sonderzeichen escaped werden) umwandeln. Der 2. Parameter bei der Intialisierung des URLClassLoader ist nicht notwendig. (Constructs a new URLClassLoader for the specified URLs *using the default delegation parent ClassLoader.*)


----------



## Thomas Darimont (20. April 2010)

Hallo,

schau mal hier:
http://www.tutorials.de/forum/java/357126-wieder-mal-java-und-plug-ins.html
http://www.tutorials.de/forum/java/310207-eine-art-plugin-system.html
http://www.tutorials.de/forum/java/245982-jar-file-nachladen.html
http://www.tutorials.de/forum/java/...ate-equinox-osgi-eclipse-extensionpoints.html

Gruß Tom


----------



## Unicate (20. April 2010)

OK Danke der ServiceLoader ist glaub ich genau nach was ich suche.

Aber: Wie kann ich Services dann dynamisch aus 2 oder mehr Projekten laden?


----------



## miffi (21. April 2010)

Howdie.

Ich hab praktisch identischen Code, wie du in deinem Beispielcode gezeigt hast, Unicate. Momentan arbeite ich an einem größeren Projekt, in dem einzelne Module nachladbar sein sollen.
Bei mir ist jetzt das Problem, dass ein _AbstractMethodError_ geworfen wird, obwohl ich über

```
// RCBModule ist das Interface, das ich für ladbare Module benutze
RCBModule module = (RCBModule)classLoader.loadClass(modulePackagePath).newInstance();
```
eigentlich eine Instanz erzeugt haben müsste, oder? Dieser Fehler wird ja eigentlich nur geworfen, wenn eine nicht überschriebene Methode einer abstrakten Klasse (oder wie hier eines Interfaces) aufgerufen wird.
Oder muss ich den Konstruktur der implementierenden Klasse auf irgendeine Art und Weise explizit aufrufen?

Falls alle Stricke reißen muss ich halt doch auch den ServiceLoader benutzen... Wäre aber dankbar für Tipps.

Gruß
miffi


----------



## Kai008 (21. April 2010)

Thrown when an application tries to call an abstract method. Normally, this error is caught by the compiler; *this error can only occur at run time if the definition of some class has incompatibly changed since the currently executing method was last compiled.*

Ich lade die Klassen Problemlos mit dem URLClassLoader aus einer Jar, die ich aus einen übertragenen String der ein Server an den/die Clienten übermittelt.


```
public Plugin(File pluginFile)
{
	super();
	
	try
	{
		//TODO Hardcodet, read the Classes from File in Jar
		URLClassLoader classLoader = new URLClassLoader(new URL[] {
			pluginFile.toURI().toURL()
		});
		Class<?> loadetClass = classLoader.loadClass("core.JobExecuter");
		abstractJobExecuter = (AbstractJobExecuter) loadetClass.newInstance();
	}
	catch (MalformedURLException e)
	{
		e.printStackTrace();
	}
	catch (ClassNotFoundException e)
	{
		e.printStackTrace();
	}
	catch (InstantiationException e)
	{
		e.printStackTrace();
	}
	catch (IllegalAccessException e)
	{
		e.printStackTrace();
	}
}
```

Ist das dass, was du unter dynamischen Nachladen verstehst?


----------



## miffi (22. April 2010)

Hallo Kai,

hab den Teil der Exception-Beschreibung wohl überlesen, dass der Fehler zur Laufzeit eine andere Bedeutung hat. Wobei es eigentlich logisch ist, bei dem Zugriff auf die Member einer abstrakten Klasse hätte der Compiler ja schon gemeckert.

Hab alle Module gecleant und neu kompiliert, jetzt geht alles. 
Danke 

Gruß
miffi


----------



## Jellysheep (22. April 2010)

Ich habe etwas ähnliches ausprobiert und mit diesem Code eine Klasse geladen: 

```
URL url = jarFile.toURI().toURL();
URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
Class<?> loadedClass = classLoader.loadClass(className);
Plugin plugin = (Plugin)loadedClass.newInstance();
```
Danach lässt sich das jarFile aber nicht mehr löschen. Wie kann ich die Schreibrechte darauf wieder erlauben?
(Wenn ich die letzten beiden Zeilen weglasse, funktioniert das Löschen)


----------



## Kai008 (22. April 2010)

Versuche es mal mit deleteOnExit(). Vorsicht, Memleak.


----------



## miffi (22. April 2010)

Das Problem wird wohl sein, dass die Klasse aus dem Jar wegen des _loadClass_ Aufrufs noch geladen ist.
Warum willst du denn das JAR zur Laufzeit löschen? Ich weiß nicht, ob es eine Möglichkeit zum "Entladen" einer Klasse gibt. Wenn du den Default-ClassLoader genommen hast, müsste dieser IMHO doch bis zur Terminierung der JVM erhalten bleiben. D.h., du müsstest die geladenen Klassen wieder rauswerfen, sonst geht das vermutlich nicht...
Würde mich auf jeden Fall auch interessieren, wenn du eine Lösung gefunden hast.

Gruß
miffi


----------



## Jellysheep (22. April 2010)

Danke für die schnellen Antworten. 
Anscheinend kann man einmal geladene Klassen nie mehr entladen. 
An deleteOnExit() hab ich auch schon gedacht, es funktioniert aber widererwartend nicht. 

Ich wollte das jar-File nur temporär aus einer eigenen Plugin-Datei entpacken und so schnell wie möglich wieder löschen. 
Ich speichere jetzt aber alle Plugins in einem extra Ordner, in dem sie bleiben können.


----------



## Kai008 (22. April 2010)

Würde ich sowieso so machen, nächstes mal musst du nur Filesize und/oder Checksum vergleichen.


----------

