Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder ein alternativer Browser verwenden.
Alle Klassen ermitteln, die Interface implementieren / Reflection
wie kann ich in herausfinden, welche Klassen ein bestimmtes Interface implementieren? Angeblich soll es mit Java Reflection funktionieren, habe aber noch nicht herausgefunden wie. Hat jemand vielleicht ein Beispiel parat oder ist die Antwort auf meine Frage gar nicht so banal?
du müsstest also über alle klassen die der ClassLoader geladen hat *kommt man da überhaupt ran ?* durchiterieren und prüfen ob das im returnten Class-array mit [c]getSimpleName()[/c] der name deines interfaces auftaucht
Wenn du dieses nur in der Eclipse wissen möchtest, dann öffne dein Interface, öffne das Kontextmenü und klicke auf "Open Type Hierachy". Dann werden dir alle Klassen angezeigt, die dieses Interface implementieren.
Wenn das nicht deine Frage beantwortet, dann solltest du deine Frage etwas genauer beschreiben was du machen möchtest.
ja sorry ... habe mich in der doc verklickt ... dachte wäre in java.lang.reflect .. war aber in werklichkeit nur in java.lang ... und natürlich gibt es java.lang.reflect.Class nicht *warum ist mir das nicht aufgefallen ?* und natürlich muss es java.lang.Class heißt ...
da aber java.lang das base-package ist *z.b. Object, Class, System, Runtime, Thread, Throwable, Exception und Error* braucht man dies nicht explizit implementieren
wofür du das nun aber brauchst wird leider trotzdem nicht klar ...
@knoppers
VORSICHT : funktioniert NUR wenn du auch wirklich ne instanz einer klasse hast ...
bei Class.getInterfaces() reicht es wenn du durch ein objekt von Class<?> eine referenz auf die klasse selbst im ClassLoader hast ... brauchst also keine instanz der klasse selbst *was z.b. bei factory-klassen unmöglich wäre wenn diese nach singleto-pattern mit privatem null-konstruktor programmiert sind*
@knoppers
VORSICHT : funktioniert NUR wenn du auch wirklich ne instanz einer klasse hast ...
bei Class.getInterfaces() reicht es wenn du durch ein objekt von Class<?> eine referenz auf die klasse selbst im ClassLoader hast ... brauchst also keine instanz der klasse selbst *was z.b. bei factory-klassen unmöglich wäre wenn diese nach singleto-pattern mit privatem null-konstruktor programmiert sind*
gut .. sowas löst an aber in der regel anderst da man bei sowas in der regel vorgibt wie dem system bekannt zu machen ist welche klasse das interface implementiert *und wenns nur über einen missbrauchten "main-class" eintrag im manifest ist *was man mit dem java.util.jar-package auslesen kann*
Man kann sich auch eine kleine Config schreiben, in welcher steht, was zu laden ist und wo es sich befindet. XML würde sich da sehr eignen. Denn das Konstrukt wird normalerweise immer komplexer, als man anfangs dachte
das ist die innere klasse PluginFilenameFilter *welche in meinem code vorher instanziert wird*
Java:
static final class PluginFilenameFilter implements FilenameFilter
{
public boolean accept(File path, String name)
{
if(name.toLowerCase().endsWith(".jar"))
return true;
else
return false;
}
}
dazu gehören dann noch diese interfaces
Java:
public interface Plugin
{
public void loadPlugin(PluginLoader loader) throws Exception;
public void startPlugin() throws Exception;
public void stopPlugin() throws Exception;
}
Java:
public interface PluginLoader
{
public Object invoke(String pluginName, String pluginMethod, Object... args) throws Exception;
public String getPath();
public String getVersion();
public void shutdown();
}
ein sample-plugin
Java:
public class zGUIShow implements Plugin
{
private PluginLoader pluginLoader=null;
public void loadPlugin(PluginLoader pluginLoader) throws Exception { this.pluginLoader=pluginLoader; }
public void startPlugin() throws Exception
{
//code
}
public void stopPlugin() throws Exception { }
}
das ganze kannst du dann in JAR-files verpacken ...
in die plugin-jars muss zusätzliche eine datei welche auf "Plugin.rsf" endet und den namen der plugin-klasse samt package enthält
also z.b. : "plugins.gui.zGUIShow"
falls du genaue erklärungen zum code brauchst *ist leider un-doc da ich ihn mal eben so aus meiner code-sammlung kopiert hab* kannst du dich gerne via icq : 345-169-219 melden ... hab das ganze mit hilfe des Java @ tutorials.de: Tutorials, Forum & Hilfe - forums in langer arbeit entworfen und weitreichend getestet ...
auch bei beispielen kannst du gerne noch mal nachfragen ... hab im aktuellen projekt genug davon rumfliegen ...
nur eine fertige lib als JAR zum einbinden in eine IDE hab ich noch nicht ... könnt ich dir aber zusammenbauen
habe ich mit dem user "Thomas Diramont" im tut.de - forum erörtert ...
problem : es muss beim start von java der CP manuell auf alle gewünschten jars festgelegt werden damit der service loader diese finden kann ... was gerade beim dynamischen laden/entladen von plugins hinderlich ist
wie gesagt ... die basis des codes stammt nicht von mir allein ... da haben bestimmt 20 user über 3 wochen dran gesessen um was halbwegs vernünftiges auf die beine zu stellen ...
ich hatte es nur so schnell parat da ich damit viel arbeite und es auch in meinem aktuellen projekt wieder eine große rolle spielt ...
alternativ melde dich mal im tut.de forum und sprich das "plugin-system" des users "SPiKEe" an *müsste mal die links der threads raussuchen"
habe ich mit dem user "Thomas Diramont" im tut.de - forum erörtert ...
problem : es muss beim start von java der CP manuell auf alle gewünschten jars festgelegt werden damit der service loader diese finden kann ... was gerade beim dynamischen laden/entladen von plugins hinderlich ist
Biste da sicher? Wozu gibts dann die ServiceLoader.load(Class<S> service, ClassLoader loader) Methode? Sollte das nicht klappen, kann man auch den Classpath zur Laufzeit um Eintraege erweitern (URLClassLoader.addURL).
genau DAS habe ich mit genanntem user lang und breit erörtert und auch selbst sehr viel getestet ...
zumindest in J6 und den pre-builds von J7 ging genau DAS eben NICHT ... habe es seit dem final von J7 auch nicht noch mal versucht ...
das war auch der grund warum ich dort damals eine thread losgetreten habe um ein halbwegs vernünftiges plugin-system zu entwickeln ... anfangs wurde auch ich auf den ServiceLoader verwiesen ... da ich aber beispiele posten konnte die damals auch bei anderen nicht funktionierten hat sich dann die gruppe der interessenten *mich eingeschlossen* daran gemacht etwas eigenes zu entwickeln ...
@TO
ich weis das der code so erstmal nicht schön aussieht *throws Exception* ...
es macht aber durch aus sinn durch den reflection-ähnlichen aufbau alle fehler bis zum caller weiter zu werfen so lange man das aufgetretene problem nicht innerhalb der methode in der es aufgetreten ist vollständig beheben kann und eventuell eine meldung an den user machen muss
@schalentier
mal noch ein anderes beispiel wesshalb ServiceLoader für dynamische systeme ausscheidet
mach dir mal den spass den aktuellen mysql-jdbc-driver zu laden ...
den packst du an einen ort der NICHT in deinem classpath steht ...
dann nimmst du folgenden code
Java:
import java.io.*;
import java.net.*;
import java.sql.*;
public class Test
{
public static void main(String[] args) throws Exception { (new Test()).test(); }
private Test() { }
private void test() throws Exception
{
URLClassLoader cl=new URLClassLoader(new URL[] { (new File("G:/java/lib/mysql-con.jar")).toURI().toURL() });
cl.loadClass("com.mysql.jdbc.Driver");
Connection con=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mysql", "root", "mysql");
con.close();
}
}
und compilest ihn ...
dann machst du eine instanz von CMD.exe auf und löschst erstmal temporär deinen classpath durch eingabe von
Code:
set CLASSPATH=
dadurch wird der variable CLASSPATH für diese instanz kein wert zugewiesen und sie wird aus der liste entfernt *durch
Code:
echo %CLASSPATH%
zu prüfen ... wenn du alles richtig gemacht hast wird %CLASSPATH% ausgegeben*
dann startest du die klasse *natürlich vor dem compilen pfad und user angepasst*
du wirst überrascht sein das du eine SQLException bekommst das angeblich kein brauchbarer treiber vorhanden sei obwohl du diesen durch einen URLClassLoader geladen hast *da das DriverManager.register() in einem static-block von Driver steht sollte dieser ja eigentlich registriert werden*
der grund liegt daran das java nur jdbc-driver verwenden kann welche zum zeitpunkt des starts der vm auch im classpath liegen ... *was ja durch die explizite aufhebung des classpath's ausgeschlossen wird*
und so ein ähnliches problem würdest du auch beim ServiceLoader erhalten wenn die zu ladenen plugins beim start NICHT im classpath liegen ...
nun kann man sich bei beiden zum glück abhilfe verschaffen ...
zum jdbc-driver *für java7* :
Java:
import java.io.*;
import java.net.*;
import java.sql.*;
import java.util.*;
import java.util.logging.*;
public class DummyDriver implements Driver
{
private Driver driver=null;
public DummyDriver(Driver driver) { this.driver=driver; }
public boolean acceptsURL(String url) throws SQLException { return driver.acceptsURL(url); }
public Connection connect(String url, Properties info) throws SQLException { return driver.connect(url, info); }
public int getMajorVersion() { return driver.getMajorVersion(); }
public int getMinorVersion() { return driver.getMinorVersion(); }
public Logger getParentLogger() throws SQLFeatureNotSupportedException { return driver.getParentLogger(); }
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { return driver.getPropertyInfo(url, info); }
public boolean jdbcCompliant() { return driver.jdbcCompliant(); }
}
wenn du nun folgenden code verwendest
Java:
import java.io.*;
import java.net.*;
import java.sql.*;
public class Test
{
public static void main(String[] args) throws Exception { (new Test()).test(); }
private Test() { }
private void test() throws Exception
{
URLClassLoader cl=new URLClassLoader(new URL[] { (new File("G:/java/lib/mysql-con.jar")).toURI().toURL() });
Object o=cl.loadClass("com.mysql.jdbc.Driver").newInstance();
DriverManager.registerDriver(new DummyDriver((Driver)o));
Connection con=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mysql", "root", "mysql");
con.close();
}
}
wirst du sehen das die verbindung fehlerfrei hergestellt werden kann
das liegt daran das du den jdbc-driver-loader damit austrickst das eine Driver-klasse während des startens der VM im classpath liegt ...
diese kannst du auf grund ihrer beschaffenheit das sie lediglich alle aufrufe an den darunterliegenden driver druchleitet zum laden von jdbc-drivern während der runtime nutzen ...
und ähnlich verhält es sich auch mit einem plugin-system und dem oben von mir gepostetem code ...
dadurch das du gegen das interface Plugin programmierst ... welches ja zum start der VM im classpath liegt kannst du jede intanz die durch einen URLClassLoader geladen wurde durch diese klasse kapseln und mit ihr arbeiten ...
ich hoffe ich konnte dir was deinen hinweis auf den ServiceLoader und die damit verbundenen schwierigkeiten beim dynamischen laden angeht etwas neues beibringen ...
Ja klar, du machst das dort auch nicht richtig ;-) Der URLClassLoader, den du dir dort erzeugst, wird ja nicht vom SystemClassLoader benutzt.
So funktionierts und auf analogem Wege sollte es auch mit dem ServiceLoader klappen:
Java:
public class LoaderTest {
public void addURL( URL url ) {
URLClassLoader loader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class cls = URLClassLoader.class;
try {
Method method = cls.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(loader, url);
} catch (Throwable t) {
t.printStackTrace();
}
}
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, SQLException {
try {
ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");
} catch( ClassNotFoundException e) {
System.out.println("NOT FOUND");
}
LoaderTest l = new LoaderTest();
l.addURL(new File("mysql-con.jar").toURI().toURL());
ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");
Connection con= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mysql", "root", "mysql");
System.out.println(con);
con.close();
}
}
Achtung, Code is HAAAAESSLICH ;-)
Ausgabe:
Code:
NOT FOUND
Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1116)
at com.mysql.jdbc.MysqlIO.<init>(MysqlIO.java:344)
at com.mysql.jdbc.ConnectionImpl.coreConnect(ConnectionImpl.java:2332)
at com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2369)
at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2153)
at com.mysql.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:792)
(Ich hab hier keine mysql Datenbank, aber der Connect Aufruf wurde vom mysql Treiber versucht)
ja klar ... funktioniert ... ist aber wirklich nicht schön ...
und meines erachtens um ein plugin-system zu bauen *wofür man eh interfaces braucht* mit reflections am SystemClassLoader rumspielen ... ja klar : würde mit sicherheit auch in richtung ServiceLoader funktionieren ... *habs jetzt leider nicht ausgetestet da mir ein KSKB dafür auch grad leider nicht einfällt* ... aber ich denke das das in falsche richtung für eine doch sehr simple aufgabe geht ...
wie gesagt : es wäre im weitesten sinne nur eine nachträgliche manipulation des classpath während der runtime ... und dafür gibt es ja eigentlich den URLClassLoader : um eben während der runtime daten dynamisch nachzuladen *das es hier und da natürlich kleine schwierigkeiten gibt ist klar* ...
aber hab trotzdem wieder was gelernt =D ... *symbolisch DANKE-button drück*
ok hab nicht alles genau gelesen, daher vielleicht redundant.
Wenn man schon ein PluginSystem entwickelt, dann sollte man einfach gewisse Regeln aufsetzen, wie alle Plugins muessen in einen Folder rein (notfalls in der Applikation ein "Install Plugin" Aktion anbieten die das erledigt. In dem Jar selber muss im manifest oder irgendeiner Datei die Klasse angegeben werden, die das PluginInterface implementiert angegeben werden.
Alles andere ist imo unsinn wenn man schon ein PluginSystem baut
ok hab nicht alles genau gelesen, daher vielleicht redundant.
Wenn man schon ein PluginSystem entwickelt, dann sollte man einfach gewisse Regeln aufsetzen, wie alle Plugins muessen in einen Folder rein (notfalls in der Applikation ein "Install Plugin" Aktion anbieten die das erledigt. In dem Jar selber muss im manifest oder irgendeiner Datei die Klasse angegeben werden, die das PluginInterface implementiert angegeben werden.
Alles andere ist imo unsinn wenn man schon ein PluginSystem baut
wobei gerade das mit "im manifest" angeben dank java.util.jar.Manifest sehr einfach ist ...
zur not könnte man sogar soweit rumtricksen das man die plugins als runnable jar baut und sich dann im loader den main-class eintrag holt *sind ide's in der lage runnable jar mit einer main-class zu erzeugen welche KEIN public static void main(String[]) enthält ?*
oder man nutzt halt andere key's ...
ist auf jeden fall besser als mein rumgemurkse da mit durchs jar laufen und versuchen irgendein file zu laden ...
ich werds mal soweit umbauen ... macht auch irgendwie mehr sinn ...
ich denke doch das wir mitlerweile wissen was TO vorhat und wie man das eleganz lösen kann ...
sowohl dein quote als auch die antwort sind zeitlich etwas veraltet und so nicht mehr relevant
Damit funktioniert das ziemlich gut. Das Problem ist natürlich immer die Zeit, die dafür benötigt wird. Darum sollte man das vorsichtig und paketeinschränkend verwenden.
Damit funktioniert das ziemlich gut. Das Problem ist natürlich immer die Zeit, die dafür benötigt wird. Darum sollte man das vorsichtig und paketeinschränkend verwenden.
fuer mich klingt das ein bisschen wie "habe einen hammer gefunden, nun ist alles ein Nagel". Wie schon geschrieben, nur weil etwas moeglich ist, sollte man sich mehrfach ueberlegen, ob es auch das richtige fuer einen ist.
Und fuer das Szenario hier ist es def. nicht die erste Wahl
Ich raffs irgendwie nicht. Ich hab doch nun schon ein Stueckel Code gepostet, wie man das machen kann, damit es mit dem ServiceLoader funktioniert. Wieso willst du/wollt ihr das nun doch alles selbst implementieren? Wo ist der Vorteil?
der vorteil ist das wir den nachteil den du mit der reflection-manipulation des systemclassloaders erzeugst umgehen ...
wie gesagt : ja - es ist proof-of-concept ... es funktioniert ... aber wenn du schon sowas machst um alleine die plugins zu laden *welche ja dann im endeffekt auch nur ein gemeinsames interface implementieren *Service** dann würde es darauf hinaus laufen das dann das gesamte system mit reflections vollgestopft wird ... und das ist auch nicht gerade die beste wahl ...
es ist halt einfacher mit zwei interfaces , einem loader und plugin-implementierungen etwas dynamisches zubasteln als mit dem ServiceLoader ... welcher wie ich bereits gesagt habe den nachteil entweder muss man den classpath beim start angeben ... oder über solche "tricks" den system-CL manipulieren ...
@Schalentier: Ich raff's auch nicht, denn faktisch funktioniert der ServiceLoader, sogar ohne die grossen Verrenkungen, die du in deinem Code anstellst.
Man benötigt lediglich Kenntnis über ein spezielles API genannt ServiceProviderInterface (SPI). Na, klingelts?
Hier mal der ClassLoader mit dem ich alle möglichen Datentypen zur Laufzeit lade sofern dafür ein Codec gefunden wurde.
Java:
package datatypes;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarEntry;
import datatypes.codecs.system.JarFile;
final class DTClassLoader
extends URLClassLoader
{
private static final String SERVICE = "META-INF/services/datatypes.CodecProviderService";
DTClassLoader()
{
super(new URL[0], ClassLoader.getSystemClassLoader());
}
protected void registerDatatypes(URL url)
throws Exception
{
JarFile jar = DataType.getFile(url); // ist nicht das JarFile auf welches die Forum API-DOC zeigt!
jar.extract(new JarEntry(SERVICE));
addURL(url);
}
}
Leider ist er so nicht zum testen geeignet, da er Teil einer bereits recht umfangreichen API ist.
[EDIT]@Schalentier: Da fällt's mir auf... kann es sein, das du auch den gerne genommenen Kardinalfehler begehst und diese [c]META-INF/services[/c]-Geschichte einfach missachtest?
@all: Tut ihr dieses vllt. sogar alle? :bahnhof:[/EDIT]
@Spacerat
gut ... und dein code ist jetzt WO GENAU anderst als meiner ?
der einzige unterschied ist das du anstatt ein eigenes interface eines aus der API nimmst und anstatt nach einem eigenen file / dem manifest zu suchen den service-descriptor verwendest ... macht summa sumarum auch nichts anderes als mein code oder der reflection-"hack" des System-CL
btw : ich habe mich wie gesagt nur soweit mit der ganzen ServiceLoader sache befasst um sagen zu können das zum zeitpunkt meines letzten tests das problem beim dynamischen laden OHNE manipulation des system-CL bestand ... verdeutlich auch durch das jdbc-driver beispiel ...
da es in einem dynamischen system eher unerwünscht ist parameter anzugeben *z.b. modifizierter CP* bzw diese durch reflections während der runtime zu manipulieren ... ist es eh ein etwas krasser eingriff ins system nur um eine klasse dynamisch zu laden ...
das geht auch einfacher mit nem URLClassLoader und einem dem loader und damit der VM bekannten interface ... weil nichts anderes ist der serviceloader wenn man ihn mal aus ein ander nehmen würde ... mit dem unterschied das dieser intern von java beim start durch die app gejagt wird und man ihn nicht selber schreiben muss ...
@TO
da du dich bis jetzt nicht gemeldet hast und wir uns hier nur noch gegenseitig mit "hacks" der VM belagern wäre mal eine stellungnahme deiner seits ganz hilfreich um den sinn hinter der diskusion noch zu haben ...
dir wurden jetzt 3 möglichkeiten gegeben
1) ServiceLoader
2) manipulation des system-CL
3) eigener code
alle 3 funktionieren ... und sollte man nach möglichkeit nicht mischen *hab da schon einiges gesehen ... dann hast du plötzlich 2 oder 3 instanzen der selben klasse ... ist auch eher sub-optimal* ... aber ohne konkrete fragen zur umsetzung und einer entscheidung welches system du nun benutzen möchtest wird es dir eher weniger bringen wenn wir hier mal eben die VM auseinander nehmen ...
daher nur die bitte *@rest : bitte nicht als destruktive kritik verstehen* : sag uns einfach welche der möglichkeiten du dir ausgesucht hast / für dich am ehesten in frage kommt ... und dann können wir uns speziell darauf konzentrieren ...
ich möchte mich hier nur ungern mit dem rest anlegen *mal wieder* ohne wenigstens eine antwort zu bekommen ob meine vorschläge (nicht) gebraucht werden ...
Damit da keine Misverständnisse aufkommen... die Zeilen 5, 7, 13, 23 und 24 können getrost aus meinem Code entfernt werden, denn sie prüfen nur die Presents dieses Eintrags. Ohne diese Prüfung würde jede beliebige URL schlicht geaddet, auch die, die keinen Service-Eintrag haben. Über den ServiceLoader aber bekommt man sie aber trotzdem erst, wenn dieser Eintrag vorhanden ist - ganz ohne Reflection und ganz ohne andere Tricks. Ich habe auch alles zunächst alles über Reflection gelöst, bis ich mir zwecks Inspiration Quellcode von [c]java.sound.sampled.spi[/c] und [c]java.imageIO.spi[/c] näher betrachtete. Getestet hab' ich das dann auch zur Genüge, denn es gibt nur noch sehr wenig Dateien (in der Theorie nämlich gar keine, kommt halt auf die registrierten Codecs an), welche ich nicht per [c]Datatype.getFile(URL url)[/c] geladen und instanziert bekomme.
[EDIT]
[IRONIE]"die alle samt und sonders auch nicht mal annäherd funktionieren"[/IRONIE] hast du vergessen zu erwähnen :lol:
...und zum "mit dem Rest anlegen"... Nun ja, mit mir nicht, warum auch, mir ist deine bzw. eure "fehlerhafte" Nutzung des SPI hinlänglich bekannt und mehr als diesen Hinweis auf diesen dubiosen [c]META-INF/service[/c]-Eintrag habe ich ohnehin nicht.[/EDIT]
Sorry für den Doppelpost, aber ich hab' mich mal daran versucht, euch die "Magie" des SPIs ([c]META-INF/service[/c]) näher zu bringen.
1. Klassen des PlugInLoaders: (2; das PlugIn-Interface und der PlugInProviderService
1.1. Der PlugInProviderService
Java:
package spiplugin.loader;
import java.util.List;
public abstract class PlugInProviderService
{
public abstract List<PlugIn> getPlugIns();
}
2. Klassen für PlugIn Tests
2.1. SimpleTest1 (Single PlugIn)
Java:
package spiplugin.plugintest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import spiplugin.loader.PlugInProviderService;
import spiplugin.loader.PlugIn;
public class SimpleTest1
extends PlugInProviderService
{
private static final List<PlugIn> plugins;
static {
ArrayList<PlugIn> rc = new ArrayList<PlugIn>();
rc.add(new PlugIn()
{
@Override
public void stop()
{
System.out.println("Single PlugIn stopped");
}
@Override
public void start()
{
System.out.println("Single PlugIn started");
}
});
plugins = Collections.unmodifiableList(rc);
}
@Override
public List<PlugIn> getPlugIns()
{
return plugins;
}
}
2.2. SimpleTest2 (10 PlugIns)
Java:
package spiplugin.plugintest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import spiplugin.loader.PlugInProviderService;
import spiplugin.loader.PlugIn;
public class SimpleTest2
extends PlugInProviderService
{
private static final List<PlugIn> plugins;
static {
final MutableInt number = new MutableInt();
ArrayList<PlugIn> rc = new ArrayList<PlugIn>();
for(int n = 0; n < 10; n++) {
rc.add(new PlugIn()
{
private final int value;
{
value = number.value++;
}
@Override
public void stop()
{
System.out.println("PlugIn " + value + " stopped");
}
@Override
public void start()
{
System.out.println("PlugIn " + value + " started");
}
});
}
plugins = Collections.unmodifiableList(rc);
}
@Override
public List<PlugIn> getPlugIns()
{
return plugins;
}
private static final class MutableInt
{
private int value;
}
}
3. Eine Test Klasse
Java:
package spiplugin;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ServiceLoader;
import spiplugin.loader.PlugIn;
import spiplugin.loader.PlugInProviderService;
public class SPIPlugInTest
{
public static void main(String[] args)
throws Throwable // Tests bitte ohne Try-Catch ;)
{
// Ermitteln des nächst höheren Verzeichnisses.
// wenn man keinen ClassPath angibt, dürfte dieses
// standardmässig kaum darin enthalten sein.
final URL parentPath = new File("..").getAbsoluteFile().toURI().toURL();
// instanzieren des eigenen URLClassLoaders
final MyURLClassLoader classLoader = new MyURLClassLoader();
// hinzufügen der Plugin-Archive, welche sich
// im parentPath befinden sollten.
classLoader.addURL(new URL(parentPath, "plugIns1.jar"));
classLoader.addURL(new URL(parentPath, "plugIns2.jar"));
// und jetzt... TESTEN
for(PlugInProviderService pps : ServiceLoader.load(PlugInProviderService.class, classLoader)) {
for(PlugIn pi : pps.getPlugIns()) {
pi.start();
pi.stop();
}
}
}
// Der hauseigene URLClassLoader... (freischalten der add-Methode)
private static final class MyURLClassLoader
extends URLClassLoader
{
private MyURLClassLoader()
{
super(new URL[0]);
}
@Override
protected void addURL(URL url)
{
super.addURL(url);
}
}
}
4. PlugInService Descriptoren (jeweils die Datei im PI-Archiv [META-INF/services/spiplugin.loader.PlugInProviderService[/c]
4.1. plugIns1.jar
Code:
spiplugin.plugintest.SimpleTest1
4.2. plugIns2.jar
Code:
spiplugin.plugintest.SimpleTest2
5. Klassen und Dateien der Archive
5.1. Hauptarchiv (muss logischerweise im CP sein)
- Die Testklasse mit der "main()"
- Alle Klassen des Paketes spiplugin.loader
Das Archiv evtl. startbar machen.
5.2. PlugInArchive
Lt. Testklasse ein Verzeichnis über dem Hauptarchiv und dafür sorgen, dass dieses nicht im CP auftaucht, damit der Effekt sichtbar wird.
5.2.1. PlugIns1.jar
- Die PlugInProvider Klasse SimpleTest1
- Der erste Servicedescriptor (4.1)
5.2.1. PlugIns2.jar
- Die PlugInProvider Klasse SimpleTest1
- Der zweite Servicedescriptor (4.2)
6. Ausgabe
Code:
PlugIn 0 started
PlugIn 0 stopped
PlugIn 1 started
PlugIn 1 stopped
PlugIn 2 started
PlugIn 2 stopped
PlugIn 3 started
PlugIn 3 stopped
PlugIn 4 started
PlugIn 4 stopped
PlugIn 5 started
PlugIn 5 stopped
PlugIn 6 started
PlugIn 6 stopped
PlugIn 7 started
PlugIn 7 stopped
PlugIn 8 started
PlugIn 8 stopped
PlugIn 9 started
PlugIn 9 stopped
Single PlugIn started
Single PlugIn stopped
Bei Fragen einfach fragen... viel Erfolg
[EDIT]Zu guter Letzt sei noch erwähnenswert, dass das Interface [c]PlugIn[/c] eigentlich überflüssig ist, solange die PlugIns nicht andere Klassen erweitern sollen.[/EDIT]
gut .. auch wenn dein post schon etwas her ist möchte ich noch mal ein grundlegendes problem einwerfen
java hat leider probleme bei der korrekten unterstützung von URLs ...
wenn man in nem pfad z.b. leerzeichen hat werden diese ja noch halbwegs korrekt mit %20 codiert ... so bald es aber an sonderzeichen geht gibts probleme ...
alles was im ASCII steht geht noch ... aber spätestens bei deutschen umlauten wird es katastrophal ...
wenn man sich mal die ausgabe von File.toURI().toURL() eines pfades ansieht der z.b. leerzeichen , deutsche umlaute , das euro-zeichen und meinet wegen noch andere ungültige zeichen enhält ist nicht mal mehr der URLDecoder in der lage diese wieder richtig darzustellen ...
am schlimmsten fällt dies unter Windows auf ... da wir ja alle wissen das Windows sowas grundsätzlich in Win-1251 codiert ... java sowas aber rfc getreu mit utf-8 macht kommt es zu schweren fehlern welche zu nicht mehr auflösbaren pfaden führen ...
soweit wie es meine tests gezeigt haben mag es wohl noch möglich sein eine URL welche aus einem File-objekt generiert wurde zu verwenden ... so bald man aber den umgekehrten weg versucht , also ein File-objekt zu erstellen dem man z.b. URL.toString() übergibt scheitern viele operationen an FileNotFoundException oder anderen ...
@Spacerat
na das beispiel ist wohl eher schlecht gewählt ... weil mehr als anonyme objekte erstellen machst du da auch nicht ...
ein richtiges beispiel für ein plugin fehlt leider bei dir ...
und ob es wirklich so sinnvoll ist [c]extends Provider[/c] anstatt [c]implements Plugin[/c] zu nutzen ... naja kommt auf den anwendungsfall an ..
@Mod
da sich TO weder bei mir noch scheinbar bei jemand anderen gemelt hat könnte man dieses thema ruhig mal als "erledigt" makieren ...
@Spacerat
na das beispiel ist wohl eher schlecht gewählt ... weil mehr als anonyme objekte erstellen machst du da auch nicht ...
ein richtiges beispiel für ein plugin fehlt leider bei dir ...
und ob es wirklich so sinnvoll ist [c]extends Provider[/c] anstatt [c]implements Plugin[/c] zu nutzen ... naja kommt auf den anwendungsfall an ..
Klassennamen sind in Beispielen [c]extends Schall[/c] und [c]implements Rauch[/c]. Provider kann man für echte Projekte auch in Plugin umbenennen, wie man es macht, bleibt jedem selbst überlassen. Die PlugIns fehlen auch nicht, sondern manifestieren sich in den Klassen SimpleTest1 und SimpleTest2. Dort werden die PlugIns zwar anonym instanziert, aber wer sagt denn, dass man das in echten Projekten so machen muss? Fakt ist, dass wenn man das SPI (ServiceLoader) verwenden will, es so verwenden sollte und nicht anders, zumindest lt. den meisten Tutorials oder gar [c]javax.sound.sampled.spi[/c] bzw. [c]javax.imageio.spi[/c]. Auf jeden Fall aber ist es besser, als irgend etwas unnötig mit Reflection verhunzen zu müssen, sogar [c]PluginClass.class.newInstance()[/c] entfällt.