Wieder mal JAVA und Plug-In's

S

SPiKEe

erstmal vorweg : ja liebe sufu-freunde ... ich habe die sufu benutzt ...
und ja : ich habe auch antworten gefunden ...
und nein : diese fürten nicht zum erfolg

worum es geht sagt schon der titel : ich möchte n plugin-system basteln ohne auf vorgefertigte frameworks zurückgreifen zu müssen

ich habe mir die varianten von Tom *ich denke ihr wisst wer gemeint ist* angesehen , welche alle auf das in JDK1.6 neue sun.misc.Service abziehlen ...
verschiedene user und auch meine erfahrungen meinten : etwas aus sun.misc.* ? ... na wenn das man gut geht ...

ich habs dann mal mit nem kleinen test-prog nach ner anleitung aus nem link getestet ... und ja ... es funktionierte ... aber es ist dann doch nicht das was ich mir vorstelle

ich weis mitlerweile durch lesen der ganzen tutorials.de-threads das der beste ansatz auf jeden fall ein einheitliches interface ist ... kein problem ... ist schnell geschrieben *kurze frage am rande ... müssen in interfaces die methoden wirklich alle PUBLIC sein ? wäre protect auch möglich ... und wenn auch nur in der implementation und nicht mal im interface selbst ?*

nun zu meinem vorhaben

eine kleine app welche als plugin-handler dient liegt in einem jar in einem verzeichnis ...
in diesem verzeichnis gibt es ein weiteres verzeichnis "plugins" ... und in diesem sollen dann die einzelnen plugins wiederum als jeweils ein jar / plugin liegen
das auslesen über (new File("./plugins")).list(); ist ja kein problem ... so weit kann ichs ja ...

nur wie lade ich nun die JAR daten ? ...
URLClassLoader *gelsen in einigen posts* scheint da ganz angebracht zu sein ... wie nutz man diesen nun aber mit REFLECTIONS *ich habe mich für reflect entschieden weil es in java.lang.* liegt und die tutorials von Sun mich annehmen lassen das es so geht* ?
oder bin ich da dann doch aufm totalen hlozweg ?

der plugin-handler soll dann halt die class instanzieren *wofür ich ja mit ClassForName n Class-objekt brauche* und eine methode invoken in der ein THIS vom plugin-handler übergeben wird *zur kommunikation untereinander* und sich die plugins je nach ihrem aufgaben-bereich beim handler anmelden *darüber müsst ihr euch nicht den kopf zerbrechen ... das läuft soweit schon ... aber halt noch nicht mit dynamisch geladenen jar-files*

wie gesagt : ich möchte halt keine verweise auf threads welche sich mit sun.misc.Service beschäftigen ...
ich habe mir viele threads zu diesem thema hier durchgelesen und es immer noch nicht begriffen ^^ ...
ich habe mich auch soweit mit Google und der java-api-doc befasst um informationen zusammeln ...

es würde vielleicht sogar reichen wenn mir jemand erklären könnte *oder einen link dazu* wie man aus geladenen JAR-files *mit URLConnection oder sowas* die klassen dann mit Class.forName(); weiterverarbeiten kann ...
ab dem punkt würde ja dann meine überlegung mit reflections schon funktionieren ...

ich weis das ich einigen mit einem erneuten thread zu diesem lang und breit diskutiertem thema ziemlich auf die nerven gehe und möchte mich dafür natürlich in höflichster form entschuldigen ...
nur bin ich scheinbar zu blöd aus den bereits vorhanden threads das wichtige zu lernen um es selbst zu versuchen ... kurz : ich hab diesen thread nur eröffnet da ich die anderen nicht begreife und erklärungs-bedarf habe =)


ps : zur kleinen info : ich arbeite bereits seit JDK1.4.2 mit java ... habe mich bis jetzt jedoch nur größtenteils mit netzwerk-programmierung beschäftigt *server-client und client-client konzepte*
von daher waren meine hauptsächlichen import anweisungen immer nur java.io.*; java.nio.*; und java.net.*;

pps : und wenn ihr mir schon tut-interne links gebt ... dann auch bitte eine grobe erklärung ... da ich aus den meisten hier einfach nicht schlau werde ..



BESTEN DANK IM VORRAUS
 
Hallo,

ich habe mir die varianten von Tom *ich denke ihr wisst wer gemeint ist* angesehen , welche alle auf das in JDK1.6 neue sun.misc.Service abziehlen ...
verschiedene user und auch meine erfahrungen meinten : etwas aus sun.misc.* ? ... na wenn das man gut geht ...

Ich weis nicht welche Beispiel du zur Verwendung von sun.misc.Service meinst...
In meinen Plugin Beispielen habe ich AFAIK NIE sun.misc.Service verwendet!

Hier:
http://www.tutorials.de/forum/java/310207-eine-art-plugin-system-2.html
habe ich nur java.util.ServiceLoader verwendet... ;-)

ich weis mitlerweile durch lesen der ganzen tutorials.de-threads das der beste ansatz auf jeden fall ein einheitliches interface ist ... kein problem ... ist schnell geschrieben *kurze frage am rande ... müssen in interfaces die methoden wirklich alle PUBLIC sein ? wäre protect auch möglich ... und wenn auch nur in der implementation und nicht mal im interface selbst ?*

Methoden an interfaces sind IMMER public - schon per Definition (deshalb kann man auch den public modifier bei Methoden-Deklarationen in interfaces weglassen).

Gruß Tom
 
Code:
start ...
z. B. start sheduler
oder start sheduler.jar

Klassenangabe über META, ziemlich simpel, weil sehr viel kann ich auch nicht, Source liegt natürlich bei.
Viel Glück.
 

Anhänge

  • Karen.zip
    244,8 KB · Aufrufe: 53
Gut Thomas,

ich möchte mich bei dir entschuldigen dass ich dir etwas unterstellt habe was du nicht getan hast ...
der fehler liegt bei mir weil ich da wohl einiges missverstanden habe ...

ich beziehe mich mit dem sun.misc.Service auf das hier :

http://www.tutorials.de/forum/java/310207-eine-art-plugin-system.html

erster post gleich der link da ...

bei dem gepostetem link wird das im unteren teil über einen URLClassLoader gemacht ... und dann über die methode sun.misc.Service.providers(Class class, ClassLoader classloader); ein iterator erzeugt über welchen man dann mit iterator.next() an die eigentlich geladene Klasse kommt um in ihr methoden aufzurufen *ich hoffe das ist einigermaßen verständlich*

wie mach ich das jetzt mit java.util.ServiceLoader ? *ja enschuldige das ich nachfrage ... aber ich werde aus der doc leider nicht schlau und kann es desshalb nicht auf das andere anwenden / das beispiel so ändern dass das ergebnis gleich bleibt*

und erlich ... aus deinen hunderten zeilen code welche du im thread auf die dein link verweist gepostet hast werde ich überhaupt nicht schlau ... ich kann mit den Generics nichts anfangen


zum thema interface : danke für die einfache und dennoch sehr verständliche antwort



@Kai008
schuldigung ... aber ich kann das ZIP leider nicht herunterladen
verschiedene wege es zu laden sowie verschiedene tools meckern das das archiv beschädigt sei ...
als inhalt erhalte ich nur die struktur

img
img\karen
img\karen\0.png < beschädigte datei > CRC32 00000000

bitte re-upp oder anderes archiv-format *z.B. RAR od. TAR* verwenden ...

btw : ich suche nach einer möglichkeit die klassen OHNE ANGABE EINES CLASSPATH AN JAVA dynamisch zu laden ...
ansonsten könnte ich die JAR-files einfach in CLASSPATH schreiben und die klassen mit reflections aufrufen
 
Warum machst du's nicht auf die einfache Weise?

1) Ein von jedem PlugIn zu implementierendes Interface erstellen.

2) PlugIn schreiben und in eine externe .jar Packen.

3) Eine Datei im jar anlegen welche den Namen der PlugIn Klasse enthält (Oder einfach das Manifest benutzen)

4) Die jar zur Laufzeit aus dem PlugIn Ordner über einen eigenen URLClassLoader laden und eine Instanz der vorhin im Manifest ider wo auch immer angegebenen Klasse erstellen.

5) Über einen instanceof check sicherstellen, dass die erstellte Klasse das zuvor erstellte PlugIn-interface implementiert. Danach kann man das erstellte Objekt casten und weiterverwenden.

Eigentlich ganz einfach...
 
Zuletzt bearbeitet:
Eigentlich ist das auch einfach ... Hab mal meinen ersten Versuch sowas zu implementieren rausgesucht und wenns hilft, dann um so besser. Dazu sei gesagt, dass der Code nicht unbedingt der Beste ist, hoffe aber, dass er dennoch sein Zweck erfüllt.

Erst einmal das PluginInterface:
OutputPlugin.java
Java:
package pluginsystem;

public interface OutputPlugin {
	public void zeigText();
}

Das Interface definiert alle Methoden, die das Plugin implementieren soll. So, nun einige Plugins.
SagWasPlugin.java für SagWas.jar
Java:
import pluginsystem.OutputPlugin;

public class SagWasPlugin implements OutputPlugin {
	@Override
	public void zeigText() {
		Muha.sagWas();
	}
}
Muha.java für SagWas.jar
Java:
public class Muha {
	public static void sagWas() {
		System.out.println("Sag was...");
	}
}
SagAufWiedersehenPlugin.java für SagAufWiedersehen.jar
Java:
import pluginsystem.OutputPlugin;

public class SagAufWiedersehenPlugin implements OutputPlugin {
	@Override
	public void zeigText() {
		System.out.println("Auf wiedersehen");
	}
}
SagHalloPlugin.java für SagHallo.jar
Java:
import pluginsystem.OutputPlugin;

public class SagHalloPlugin implements OutputPlugin {
	@Override
	public void zeigText() {
		System.out.println("Hallo");
	}
}

So, das ist nun meine Pluginstruktur gewesen. Alle Plugin-Jars liegen im Ordner plugins. Um nun die Plugins zu laden und auszuführen, habe ich die Plugins geladen und von der Pluginklasse ein Objekt instanziert, welches dann in eine Liste kommt, in der alle Pluginobjekte verwaltet werden. Natürlich kann man auch die Klassen laden, falls man mehrere Instanzen braucht, so wie es bei mir im Praktikum der Fall war, weil ich eigene Komponenten entwickeln musste, die in solchen Jars untergebracht sein sollten, aber in der Regel reicht ein Objekt des jeweiligen Plugins.

So, hier dann der Teil, der die Plugins lädt und dann ausführt.
Main.java
Java:
package pluginsystem;



import java.io.File;

import java.net.MalformedURLException;

import java.net.URL;

import java.net.URLClassLoader;

import java.util.ArrayList;



public class Main {

	public static void main(String[] args) {

		ArrayList<OutputPlugin> list = new ArrayList<OutputPlugin>();

		File pluginDir = new File("./plugins");



		// Plugins laden

		File[] jars = pluginDir.listFiles();

		for(File jar : jars) {

			try {

				URL url = new URL("file", "localhost", jar.getAbsolutePath());

				URLClassLoader loader = new URLClassLoader(new URL[]{url});

				Class<?> clazz = loader.loadClass(jar.getName().substring(0,

						jar.getName().lastIndexOf(".")) + "Plugin");

				OutputPlugin plugin = (OutputPlugin)clazz.newInstance();



				// Plugin-Objekt in die Liste aufnehmen

				list.add(plugin);

			} catch(MalformedURLException e) {

				e.printStackTrace();

			} catch(ClassNotFoundException e) {

				e.printStackTrace();

			} catch(InstantiationException e) {

				e.printStackTrace();

			} catch(IllegalAccessException e) {

				e.printStackTrace();

			}

		}



		// Alle Plugins ausführen

		for(OutputPlugin plugin : list) {

			plugin.zeigText();

		}

	}

}
Ja, und das wars auch schon. Eine ganz simple Sache eigentlich.
 
Hallo,

um das Beispiel mit dem Service Loader vielleicht ein wenig verständlicher zu machen hier noch
ein einfacheres:

Wir bauen einen "Mini-ApplicationServer" in dem wir neue Applications (als Plugins) über
Classpath Erweiterung (zusätzliches Jar in den Classpath legen) hinzufügen wollen.

Projekt: de.tutorials.appserver:

Unser Application Interface:
Java:
package de.tutorials.appserver;

public interface Application {
  void start();
}

Der Einstiegspunkt unserers "Servers"
Java:
package de.tutorials.appserver;

import java.util.ServiceLoader;

public class Main {
  public static void main(String[] args) {
    ServiceLoader<Application> applications = ServiceLoader.load(Application.class);
    
    System.out.println("Starting applications");
    for(Application application : applications){
      application.start();
    }
  }
}

Unsere DefaultApplication die mit dem Server mitkommt:
Java:
package de.tutorials.appserver.application;

import de.tutorials.appserver.Application;

public class DefaultApplication implements Application {
  public void start() {
    System.out.println("starting default application");
  }
}

Damit der ServiceLoader die Implementierungen unseres interfaces Application finden kann
muss im Verzeichnis META-INF/services eine Datei mit dem Namen: de.tutorials.appserver.Application
liegen.

Dort sind nun die Implementierungen von Application (also unsere konkreten Applications hinterlegt).
Da wir hier nur eine Anwendung haben enthält diese Datei nur eine Zeile.

META-INF/services/de.tutorials.appserver.Application:
Code:
de.tutorials.appserver.application.DefaultApplication

Exportiert man das Projekt nun als ausführbares-jar (beispielsweise via eclipse) appserver.jar:

So ergibt sich folgendes Bild, wenn man den Appserver startet:
Code:
C:\temp\pluginSystem>java -cp appserver.jar de.tutorials.appserver.Main
Starting applications
starting default application

Soweit so gut. Nun wollen wir dem Server eine neue Anwendung hinzufügen (AnotherApplication).
Dazu legen wir ein neues Projekt an: de.tutorials.appserver.anotherApplication.
Dieses Projekt machen wir von de.tutorials.appserver abhängig.

Anschließend erstellen wir folgende Klasse: (Unsere zusätzliche Anwendung)
Java:
package de.tutorials.appserver.anotherApplication;

import de.tutorials.appserver.Application;

public class AnotherApplication implements Application {
  public void start() {
    System.out.println("starting another application");
  }
}

und folgende Datei wieder in META-INF/services/de.tutorials.appserver.Application:
Code:
de.tutorials.appserver.anotherApplication.AnotherApplication

Nun exportieren wir dieses Projekt wieder als jar (anotherApplication.jar) in das
selbe Verzeichnis wo das andere appserver.jar liegt.

Führen wir nun appserver.jar erneut aus so ergibt sich wieder:
Code:
C:\temp\pluginSystem>java -cp appserver.jar de.tutorials.appserver.Main
Starting applications
starting default application
Wir haben die neue App noch nicht in den classpath gelegt.

Tun wir das sehen wir folgendes:
Java:
C:\temp\pluginSystem>java -cp appserver.jar;anotherApplication.jar de.tutorials.appserver.Main
Starting applications
starting default application
starting another application

und schon ist die Basis für ein einfaches Plugin-System fertig. Das ist kein Rocket-Science ;-)

Möchte man das ganze nun etwas flexibler halten so kann man sich auch ein Verzeichnis plugins bzw. dropins anlegen und die zusätzlichen Anwendungen oder Plugins auch dort ablegen.

Das könnte dann so aussehen:
Code:
C:\temp\pluginSystem>tree /F /A
Auflistung der Ordnerpfade
Volumeseriennummer : D205-4274
C:.
|   appserver.jar
|
\---dropin
        anotherApplication.jar

Starten würde man das System dann via:
Code:
C:\temp\pluginSystem>java -cp appserver.jar;dropin/* de.tutorials.appserver.Main
Starting applications
starting default application
starting another application

Seit Java 6 inkludiert ein * im Classpath alle Jars in dem angegebenen Verzeichnis.
So kann man sich sämtliche Reflection / ClassLoader Geschichten sparen ;-)

Gruß Tom
 

Anhänge

  • anotherApplication.jar
    1,6 KB · Aufrufe: 79
  • appserver.jar
    3,3 KB · Aufrufe: 64
  • de.tutorials.appserver.anotherApplication.zip
    3,4 KB · Aufrufe: 66
  • de.tutorials.appserver.zip
    5,2 KB · Aufrufe: 76
Danke ihr drei ...
jetzt habe ich es endlich begriffen ...

@mccae
stimmt eigentlich ... ganz einfach ... mehr muss man eigentlich für die gesamte plugin geschichte nicht machen ...
nur hatte ich schon beim aufstellen dieses einfachen konzeptes fehler drin gehabt und mir das ganze zu kompliziert vorgestellt ...

@Akeshihiro
vielen dank für die beispiel-implementierung ...
einfach aufgebaut ... leicht zu verstehen ... funktionsfähig ... und dennoch nur diese paar zielen code ... respect *thumbs up*

@Thomas
auch sehr schöne implementierung ... ich werde versuchen es so umzusetzen ...
nur ist es wohl nicht ganz das was ich für meine anforderungen brauche weils schon wieder etwas zu PRODUKTIV gedacht ist *is aber dennoch genau so einfach wie das von Akeshihiro*
das einzige wo ich garantiert wieder gegen die wand rennen würde wäre die umsetzung von angabe des CP in die loader-klasse zu integrieren so das kein CP angegeben werden muss ...


ich werde mich mal dran machen und eine implementierung versuchen ...
sollte alles dann soweit hinhaun werd ich mal meinen code hier posten das andere was davon haben ... *wird dann sicher änlichkeit mit euren haben ... aber sich bestimmt in n paar details unterscheiden*


danke nochmal für die verständlichen erklärungen und den super referenz implementierungen ...

greedz .. SPiKEe
 
oke ... ich habe es jetzt soweit lauffähig hinbekommen ...

hier mal die src-files

a.java

Java:
package de.dfthq..utils.pih;
import java.io.*;
import java.nio.channels.*;
public class a
{
	private static boolean check=true;
	protected static final int VERSION=1000056;
	protected b bco=null;
	protected c cco=null;
	public static void main(String[] args)
	{
		for(int i=0; i<args.length; i++)
		{
			if(args[i].equals("-nocheck"))
				check=false;
		}
		new a();
	}
	protected a()
	{
		if(check)
		{
			try
			{
				FileChannel lfc=(new RandomAccessFile(new File(System.getProperty("user.home")+System.getProperty("file.separator")+".pih"), "rws")).getChannel();
				FileLock lfl=lfc.tryLock();
				if(lfl==null)
					exit();
			}
			catch(Exception e) { exit(); }
		}
		bco=new b(this);
		bco.load();
		cco=new c(this);
		try { cco.load(); } catch(Exception e) { e.printStackTrace(); }
		cco.startPlugins();
	}
	protected void exit()
	{
		System.exit(0);
	}
	public void registerPlugin(String s)
	{
		System.out.println(s);
	}
}

b.java wird hier nicht erwähnt da die klasse zur zeit nur ein JFrame aufbaut ... wird aber später für die GUI benötigt

c.java

Java:
package de.dfthq..utils.pih;
import de.dfthq..utils.lib.*;
import java.io.*;
import java.net.*;
import java.util.*;
class c
{
	protected a aco=null;
	protected ArrayList<PluginInterface> list=new ArrayList<PluginInterface>();
	protected c(a aco)
	{
		this.aco=aco;
	}
	protected void load() throws Exception
	{
		File pluginDir=new File((new File(".")).getCanonicalFile(), "plugins");
		File[] jars=pluginDir.listFiles(new FileFilter() {
			public boolean accept(File f)
			{
				return f.getName().toLowerCase().endsWith(".jar");
			}});
		if(jars.length==0)
			return;
		for(File jar : jars)
		{
			URL url=jar.toURI().toURL();
			URLClassLoader loader=new URLClassLoader(new URL[] { url });
			Class<?> clazz=loader.loadClass(jar.getName().substring(0, jar.getName().lastIndexOf("."))+"Plugin");
			PluginInterface plugin=(PluginInterface)clazz.newInstance();
			list.add(plugin);
		}
	}
	protected void startPlugins()
	{
		for(PluginInterface plugin : list)
		{
			plugin.load(aco);
			plugin.doit();
		}
	}
}

PluginInterface.java

Java:
package de.dfthq..utils.lib;
import de.dfthq..utils.pih.*;
public interface PluginInterface
{
	void load(a aco);
	void doit();
}

und hier noch die beiden beispiel-plugins captcha und login

captchaPlugin.java

Java:
import de.dfthq..utils.lib.*;
import de.dfthq..utils.pih.*;
public class captchaPlugin implements PluginInterface
{
	protected a aco=null;
	public captchaPlugin() { }
	public void load(a aco)
	{
		this.aco=aco;
	}
	public void doit()
	{
		aco.registerPlugin("CAPTCHA PLUGIN");
	}
}

loginPlugin.java

Java:
import de.dfthq..utils.lib.*;
import de.dfthq..utils.pih.*;
public class loginPlugin implements PluginInterface
{
	protected a aco=null;
	public loginPlugin() { }
	public void load(a aco)
	{
		this.aco=aco;
	}
	public void doit()
	{
		aco.registerPlugin("LOGIN PLUGIN");
	}
}

beim start der app wird erfolgreich das erwartete ergebnis ausgegeben

Code:
I:\java\pih>java -jar pih.jar
CAPTCHA PLUGIN
LOGIN PLUGIN

ich danke allen die mitgeholfen haben mir die PLUGIN-möglichkeiten zu erklären

greedz ... SPiKEe

ps : ich weis das mein programmier-stil nicht der beste ist ...
wenn jemand vorschläge zur optimierung hat bitte posten ...
 
nachtrag :

da der hier installierte BAD-WORD-FILTER meinen package-name ruiniert hatt hier noch einmal der richtige
*sorry leutz .. ich habs vorher beim klick auf VORSCHAU leider nicht gemerkt*

de . dfthq . p r o g g . utils . *

kurze frage an die mods / admins :

warum wird das kürzel pro g g vom bad-word-filter gestrichen ? ...
ich persönlich sage immer das ich pro g g e wenn ich von programieren rede ...
und mir ist ganz erlich gesagt eine andere bedeutung nicht bewusst ... falls es doch so sein sollte bitte ich mal um aufklärung ...


in diesem sinne ... SPiKEe
 
Zurück