Mini Pluginsystem - Zugriff auf andere Plugins

d4rkY89

Mitglied
Hiho,
also ich habe mich sehr für pluginartige Programme interessiert und dank einiger Beiträge hier im Forum bereits folgendes auf die Beine gestellt und bin dabei auf ein Problem gestoßen, welches ich weiter unten erläutere:

Eclipse-Projekt: SimplePluginLoader

Class: de.d4rkY89.plugin.exception.LoadPluginException

Java:
package de.d4rky89.plugin.exception;

@SuppressWarnings("serial")
public class LoadPluginException extends Exception {
	public LoadPluginException (String message) {
		super(message);
	}
	
	public LoadPluginException() {
		super();
	}
}

Interface: de.d4rkY89.plugin.interfaces.IPlugin

Java:
package de.d4rky89.plugin.interfaces;

public interface IPlugin {
	void start();
	void stop();
}

Class: de.d4rky89.plugin.loader.PluginLoader

Java:
package de.d4rky89.plugin.loader;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedList;
import java.util.List;

import de.d4rky89.plugin.exception.LoadPluginException;
import de.d4rky89.plugin.interfaces.IPlugin;
import de.d4rky89.plugin.tools.JarFileFilter;

public class PluginLoader {
	public static PluginLoader PLUGIN_LOADER;
	
	static {
		PLUGIN_LOADER = new PluginLoader();
	}
	
	public static void main(String[] args) {
		PLUGIN_LOADER.loadPlugins();
		PLUGIN_LOADER.startPlugins();
		PLUGIN_LOADER.stopPlugins();
	}

	private List<IPlugin> plugins = null;

	private PluginLoader() {
		plugins = new LinkedList<IPlugin>();
	}
	
	public IPlugin getPluginForClassName(String className) {
		for (IPlugin item : plugins) {
			if (item.getClass().getName().equals(className))
				return item;
		}
		
		return null;
	}

	private void loadPlugins() {		
		File pluginDir = new File("plugins");
		if (!pluginDir.exists())
			return;
		
		File[] pluginJars = pluginDir.listFiles(new JarFileFilter());

		System.out.println("Start loading plugins.");
		
		for (File item : pluginJars) {
			System.out.printf("Loading Plugins from %s.\n", item.getAbsolutePath());
			try {
				URL pluginURL = item.toURI().toURL();
				URLClassLoader loader = new URLClassLoader(
						new URL[] { pluginURL });

				loadPlugin(loader);
			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (LoadPluginException e) {
				e.printStackTrace();
			}
		}
		
		System.out.println("Finished loading plugins.");
	}

	private void loadPlugin(URLClassLoader loader) throws IOException, LoadPluginException {
		String urlPath = "META-INF/services/" + IPlugin.class.getPackage().getName() + ".IPlugin";
		URL serviceURL = loader.findResource(urlPath);
		
		if (serviceURL == null)
			throw new LoadPluginException(String.format("Missing \"%s\"", urlPath));
		
		BufferedReader br = new BufferedReader(new InputStreamReader(
				serviceURL.openStream()));

		List<IPlugin> containingPlugins = new LinkedList<IPlugin>();
		String pluginClassName = null;
		try {
			while ((pluginClassName = br.readLine()) != null) {
				System.out.println("Loading Plugin Class " + pluginClassName);

				Class<?> plugin = loader.loadClass(pluginClassName);
				IPlugin iPlugin = (IPlugin) plugin.newInstance();

				containingPlugins.add(iPlugin);
			}
			
			plugins.addAll(containingPlugins);
		} catch (ClassNotFoundException e) {
			throw new LoadPluginException("Plugin class not found: " + pluginClassName);
		} catch (InstantiationException e) {
			throw new LoadPluginException("Failed to create a new instance of class " + pluginClassName);
		} catch (IllegalAccessException e) {
			throw new LoadPluginException("Failed to create a new instance of class " + pluginClassName);
		}
	}

	private void startPlugins() {
		System.out.println("Starting plugins");
		
		for (IPlugin item : plugins)
			item.start();
	}

	private void stopPlugins() {
		System.out.println("Stopping plugins");
		
		for (IPlugin item : plugins)
			item.stop();
	}
}

Class: de.d4rky89.plugin.tools.JarFileFilter

Java:
package de.d4rky89.plugin.tools;

import java.io.File;
import java.io.FileFilter;

public class JarFileFilter implements FileFilter {
	@Override
	public boolean accept(File path) {
		if (!path.getName().endsWith(".jar") || path.isDirectory())
			return false;
		
		return true;
	}
}


So das waren die Klassen soweit zum "Hauptprogramm"
Jetzt habe ich noch 2 Projekte, die als Plugins dienen:

Eclipse-Projekt: HelloWorldPlugin

Class: de.d4rky89.plugin.HelloWorld

Java:
package de.d4rky89.plugin;

import de.d4rky89.plugin.interfaces.IPlugin;

public class HelloWorld implements IPlugin {
	@Override
	public void start() {
		println("Hello World!");
	}

	@Override
	public void stop() {
		println("See Ya World!");
	}
	
	public void println(String str) {
		System.out.println(str);
	}
}

Eclipse-Projekt TestPlugin

Class: de.d4rky89.plugin.TestPlugin

Java:
package de.d4rky89.plugin;

import de.d4rky89.plugin.HelloWorld;
import de.d4rky89.plugin.interfaces.IPlugin;
import de.d4rky89.plugin.loader.PluginLoader;

public class TestPlugin implements IPlugin {

	@Override
	public void start() {
		System.out.println("TestPlugin starting");
		
		IPlugin plugin = PluginLoader.PLUGIN_LOADER.getPluginForClassName("de.d4rky89.plugin.HelloWorld");
		
		HelloWorld helloWorld = (HelloWorld) plugin;
		
		helloWorld.println("This is SPARTA******");
	}

	@Override
	public void stop() {

	}
}



So. Und im TestPlugin liegt mein Problem. Ich kann mir zwar das Objekt von der HelloWorld-Instanz mit "PluginLoader.PLUGIN_LOADER.getPluginForClassName("de.d4rky89.plugin.HelloWorld");" holen, allerdings scheitert der darauf folgende TypeCast. Das Problem liegt wohl daran, dass sich die HelloWorld-Klasse nicht im Classpath befindet. Ich habe schon versucht das Programm mit folgenden Parametern zu starten, allerdings ohne Erfolg:

-cp "./plugins"
-cp "./plugins/*"
-cp "<absoluter Pfad zum Plugin-Ordner>"
-cp "<absoluter Pfad zum Plugin-Ordner>/*"
-cp "<absoluter Pfad zur HelloWorldPlugin.jar>"


Ich habe den kompletten SourceCode mal im Anhang hochgeladen.
Der Grund, wieso ich auf andere Plugins zugreifen möchte ist, dass ich später ein Hauptfenster habe, welches allerdings selber ein Plugin sein wird. Und dieses Fenster möchte ich später durch andere Plugins erweitern können. Vielleicht habt ihr ja hinsichtlich dessen, was ich eben erklärt habe ja sogar eine alternative, bessere Lösung um von Plugin auf Plugin zugreifen zu können.

MfG d4rkY89

Edit:
Die 2 Plugins befinden sich übrigens schon im Plugin-Order vom SimplePluginLoader-Projekt. Ihr müsst sie also nicht selber noch als Jar packen und im Ordner ablegen.
 

Anhänge

  • HelloWorldPlugin.zip
    2,4 KB · Aufrufe: 11
  • SimplePluginLoader.zip
    11 KB · Aufrufe: 12
  • TestPlugin.zip
    2,5 KB · Aufrufe: 10
Zuletzt bearbeitet:
also als schnelle lösung hätte da ich einen ansatz
wenn du dir mal dieses thema anschaust http://www.tutorials.de/java/357126-wieder-mal-java-und-plug-ins.html siehst du das ich unten in meinem fertigem code im Interface das alle plugins eine methode implementieren müssen der eine instanz des plugin-loaders übergeben wird ...
das fehlt bei dir ... darum wunder ich mich auch wie du überhaupt an das plugin-objekt der hello-world instanz herankommst da der aufruf eigentlich static aussieht ...
wenn du jetzt von einem plugin ein anderes steuern willst solltest du im plugin-loader eine methode implementieren auf die dann die plugins über die loader-instanz zugreifen können und ihr mitteilen können welche methode in welchem plugin mit welchen parametern auszuführen ist
dadurch sparst du dir unsaubere casts *die sowieso nicht funktionieren wenn du zur compile-time nicht die entsprechende klasse zur verfügung hast
das ganze hat etwas mit reflections zu tun ... beim ersten wirkt es sehr komplex ... aber wenn du es einmal hast ist es dann doch sehr einfach ...
 
Danke SPiKEe
ich hab das zwar schon in die Richtung versucht, was aber Anfangs nicht geklappt hatte, da ich die Methoden nicht auf die Plugin-Instanzen angewandt habe. Aber da du jetzt auch nochmal gesagt hast, dass es so gehn müsste hab ich nochmal drübergeschaut und hab einfach die richtige invoke-Methode genommen xD.

Allerdings kann ich auf diese Weise nur auf Methoden zugreifen, die sich in der jeweiligen Plugin-Klasse befinden, die das Interface "IPlugin" erben. Das heißt ja dann, dass diese Klasse in jedem Plugin die komplette Kommunikation (jedenfalls eingehende) übernimmt.

Und das mit dem statischem Feld für die PluginLoader-Instanz hab ich auch wieder ganz schnell weggemacht xD. Hatte das ganze in der Nacht vor meinem letzten Post geschrieben. Da fang ich gerne mal an komische Sachen zu machen^^

Also um es kurz zu machen habe ich meine PluginLoader-Klasse um folgendes erweitert und es funktioniert bisher ohne Probleme:

Java:
public Object invokePluginMethod(String pluginName, String methodName, Object... args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
	for (IPlugin item : plugins) {
		Class<?> clazz = item.getClass();

		if (clazz.getName().equals(pluginName)) {
			for (Method method : clazz.getDeclaredMethods()) {
				if (method.getName().equals(methodName)) {
					return method.invoke(item, args);
				}
			}
		}
	}

	return null;
}
 
Zuletzt bearbeitet:

Neue Beiträge

Zurück