@Tom
Deine Mühe in allen Ehren *nein wirklich ... du bist mit einer der besten Java-Profis die ich kenne* ... aber ganz so wollte ichs dann wieder nicht.
Was du nicht beachtest hast : KEIN CLASSPATH
In deinem Beispiel läds du alle Klassen via -cp - Angabe in den ROOT-ClassLoader ... das will ich aber nicht da ich es auch nicht garantieren kann.
Das mit dem ServiceLoader hattest du ja schon damals versucht mir nahe zu bringen *wow ... das du dir echt die Mühe gemacht und diese alten Threads ausgegraben hast* ... aber wie du ja schon selbst gesagt hast ist es auch nicht ganz das was ich will.
Das natürlich dein Beispiel so einwandfrei funktioniert ist klar.
Das stichwort Reflections trifft es noch nicht ganz ... aber es geht in die Richtung.
@Techno
Ja ... ich caste dierekt über Plugin beim Class.newInstance() welches ich auf das Class-Objekt aus dem URLClassLoader anwende.
Ich sehe schon ich muss doch mal schnell ein lauffähiges Beispiel basteln ...
*halbe Stunde später*
Ich habe es mal bewusst einfach gehalten.
Die Ordnerstruktur *root-dir ist hier H:\java\de*:
Code:
H:\java>dir /s de
Datenträger in Laufwerk H: ist sTiCk
Volumeseriennummer: A402-F5C4
Verzeichnis von H:\java\de
28.06.2011 01:41 <DIR> .
28.06.2011 01:41 <DIR> ..
28.06.2011 01:41 <DIR> tutorials
0 Datei(en), 0 Bytes
Verzeichnis von H:\java\de\tutorials
28.06.2011 01:41 <DIR> .
28.06.2011 01:41 <DIR> ..
28.06.2011 02:13 <DIR> spikee
0 Datei(en), 0 Bytes
Verzeichnis von H:\java\de\tutorials\spikee
28.06.2011 02:13 <DIR> .
28.06.2011 02:13 <DIR> ..
28.06.2011 02:11 2.156 Launcher.class
28.06.2011 02:14 2.390 Launcher.jar
28.06.2011 01:51 1.542 Launcher.java
28.06.2011 02:10 <DIR> lib
28.06.2011 02:11 795 Loader$1.class
28.06.2011 02:11 3.745 Loader.class
28.06.2011 02:14 3.148 Loader.jar
28.06.2011 02:11 2.997 Loader.java
28.06.2011 02:11 697 MainPlugin.class
28.06.2011 02:13 1.103 MainPlugin.jar
28.06.2011 02:10 337 MainPlugin.java
28.06.2011 02:13 32 MainPlugin.rsf
11 Datei(en), 18.942 Bytes
Verzeichnis von H:\java\de\tutorials\spikee\lib
28.06.2011 02:10 <DIR> .
28.06.2011 02:10 <DIR> ..
28.06.2011 02:10 268 Plugin.class
28.06.2011 01:51 179 Plugin.java
28.06.2011 02:10 272 PluginLoader.class
28.06.2011 02:10 286 PluginLoader.java
4 Datei(en), 1.005 Bytes
Anzahl der angezeigten Dateien:
15 Datei(en), 19.947 Bytes
11 Verzeichnis(se), 1.475.760.128 Bytes frei
Nun die einzelnen Files
Launcher.java
Java:
package de.tutorials.spikee;
import de.tutorials.spikee.lib.*;
import java.lang.reflect.*;
import java.io.*;
import java.net.*;
public class Launcher
{
private String PATH=null;
public static void main(String[] args) throws Exception { (new Launcher()).launch(); } //starten des Launchers
private Launcher() { setPath(); } //privater Konstruktor um Instanzierung von außen zu verhindern
private void setPath() //setzt die globale Variable PATH welche den aktuellen Pfad zum Jar-File enthält *funktioniert sowohl IN einem JAR als auch OHNE sowie unter LINUX*
{
PATH=this.getClass().getResource(this.getClass().getSimpleName()+".class").toString();
if(PATH.contains("!"))
PATH=PATH.substring(0, PATH.indexOf("!"));
PATH=(new File(PATH.substring(PATH.indexOf("/"), PATH.lastIndexOf("/")))).getAbsolutePath();
}
private void launch() throws Exception //eigentlicher launch
{
URLClassLoader urlCL=new URLClassLoader(new URL[] { (new File(PATH, "Loader.jar")).toURI().toURL() }); // URLClassLoader auf Loader.jar setzen
Class<?> Loader=urlCL.loadClass("de.tutorials.spikee.Loader"); // Klasse de.tutorials.spikee.Loader laden
Object loader=Loader.newInstance(); // Instanz erzeugen
Method[] methods=Loader.getDeclaredMethods(); // deklarierte Methoden holen
for(Method method : methods) // foreach
{
if(method.getName().equals("load")) // wenn Methoden-Name "load" ist
method.invoke(loader, (Object[])null); // Methode aufrufen *(Object[])null um Compilerwarnung zu erfüllen
}
}
}
Loader.java
Java:
package de.tutorials.spikee;
import de.tutorials.spikee.lib.*;
import java.lang.reflect.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.jar.*;
public class Loader implements PluginLoader
{
private String PATH=null;
private Vector<Plugin> plugins;
public Loader() { setPath(); plugins=new Vector<Plugin>(); }
private void setPath()
{
PATH=this.getClass().getResource(this.getClass().getSimpleName()+".class").toString();
if(PATH.contains("!"))
PATH=PATH.substring(0, PATH.indexOf("!"));
PATH=(new File(PATH.substring(PATH.indexOf("/"), PATH.lastIndexOf("/")))).getAbsolutePath();
}
public void load() throws Exception
{
File pluginDir=new File(PATH); // aktuelles Verzeichnis als File-Objekt
File[] pluginJars=pluginDir.listFiles(new FilenameFilter() // alle .jar -Files als Array mit ausnahme Launcher.jar und Loader.jar
{
public boolean accept(File path, String name)
{
if((name.equals("Launcher.jar"))||(name.equals("Loader.jar"))||(!name.toLowerCase().endsWith(".jar")))
return false;
else
return true;
}
});
for(File pluginJar : pluginJars) // foreach
{
String name="FAIL"; // Prüfvariable ob Jar-File auch verwendbares Plugin
JarFile jarFile=new JarFile(pluginJar); // Plugin als JarFile-Objekt
Enumeration<JarEntry> entries=jarFile.entries(); // alle Dateien holen
while(entries.hasMoreElements()) // durch Enumeration gehen
{
JarEntry entry=entries.nextElement();
if(entry.getName().endsWith("Plugin.rsf")) // falls eine Datei mit der Endung "Plugin.rsf" gefunden wird
name=(new BufferedReader(new InputStreamReader(jarFile.getInputStream(entry)))).readLine(); // Plugin-Name aus dieser Datei lesen
}
jarFile.close(); // JarFile closen da sonst nicht über URLClassLoader ladbar
if(name.equals("FAIL")) // prüfen der Prüfvariable , wenn immer noch "FAIL" dann ist JarFile kein verwendbares Plugin da die RSF-Datei nicht gefunden wurde
continue;
URLClassLoader urlCL=new URLClassLoader(new URL[] { pluginJar.toURI().toURL() }); // JarFile in URLClassLoader laden
Class<?> clazz=urlCL.loadClass(name); // Plugin-Klasse holen
Plugin plugin=(Plugin)clazz.newInstance(); // Instanz erzeugen
plugin.loadPlugin(this); // loadPlugin(PluginLoader) - Methode aufrufen
plugins.add(plugin); // Plugin dem Vector hinzufügen
}
for(Plugin plugin : plugins) { plugin.startPlugin(); } // alle Plugins starten
}
public Object invoke(String pluginName, String methodName, Object... args) throws Exception // invoker-Methode um von einem Plugin Methoden eines anderen ausführen zu können
{
for(Plugin plugin : plugins)
{
Class<?> clazz=plugin.getClass();
if(clazz.getName().equals(pluginName))
{
for(Method method : clazz.getDeclaredMethods())
{
if(method.getName().equals(methodName))
return method.invoke(plugin, args);
}
}
}
return null;
}
}
lib/Plugin.java
Java:
package de.tutorials.spikee.lib;
public interface Plugin
{
public void loadPlugin(PluginLoader pluginLoader) throws Exception;
public void startPlugin() throws Exception;
}
lib/PluginLoader.java
Java:
package de.tutorials.spikee.lib;
public interface PluginLoader
{
public Object invoke(String pluginName, String methodName, Object... args) throws Exception; // java.lang.reflect.Method.invoke(String, Object...) - work-around um von einem Plugin Methoden eines anderen aufzurufen
}
MainPlugin.java
Java:
package de.tutorials.spikee;
import de.tutorials.spikee.lib.*;
public class MainPlugin implements Plugin
{
private PluginLoader pluginLoader=null;
public void loadPlugin(PluginLoader pluginLoader) throws Exception { this.pluginLoader=pluginLoader; }
public void startPlugin() throws Exception { System.out.println("HELLO"); }
}
MainPlugin.rsf - Diese Datei ist lediglich eine sog. descriptor-Datei um den JAR-Namen frei wählen zu können
Code:
de.tutorials.spikee.MainPlugin
Soo ... nun zum eigentlichen Problem
Packen wir die Jar-Files erstmal wie ich es eigentlich vorhabe :
Launcher.jar
+-> de/tutorials/spikee/Launcher.class
Loader.jar
+-> de/tutorials/spikee/Loader.class
+-> de/tutorials/spikee/Loader$1.class
+-> de/tutorials/spikee/lib/Plugin.class
+-> de/tutorials/spikee/lib/PluginLoader.class
MainPlugin.jar
+-> de/tutorials/spikee/MainPlugin.class
+-> de/tutorials/spikee/MainPlugin.rsf
Und führen das ganze über
Code:
java -jar de\tutorials\spikee\Launcher.jar
aus ... dann erhalten wir das hier
Code:
H:\java>java -jar de\tutorials\spikee\Launcher.jar
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at de.tutorials.spikee.Launcher.launch(Launcher.java:27)
at de.tutorials.spikee.Launcher.main(Launcher.java:9)
Caused by: java.lang.NoClassDefFoundError: de/tutorials/spikee/lib/Plugin
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:791)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:449)
at java.net.URLClassLoader.access$100(URLClassLoader.java:71)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
at de.tutorials.spikee.Loader.load(Loader.java:48)
... 6 more
Caused by: java.lang.ClassNotFoundException: de.tutorials.spikee.lib.Plugin
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
... 18 more
Um das Beispiel nun ausführen zu können und um an die gewünschte Ausgabe "HELLO" zu kommen muss ich die Jar's jedoch so packen ... *was ich eigentlich nicht will*
Launcher.jar
+-> de/tutorials/spikee/Launcher.class
+-> de/tutorials/spikee/lib/Plugin.class
+-> de/tutorials/spikee/lib/PluginLoader.class
Loader.jar
+-> de/tutorials/spikee/Loader.class
+-> de/tutorials/spikee/Loader$1.class
MainPlugin.jar - nicht verändert.
So ... ich hoffe nach dem ich das hier mal SEHR AUSFÜHRLICH dargestellt habe mal meine eigentliche Frage stellen zu können : warum werden die Interfaces Plugin und PluginLoader nicht über den URLClassLoader mitgeladen und warum hilft auch kein expliziter Aufruf von
Java:
this.getClass().getClassLoader().loadClass("de.tutorials.spikee.lib.Plugin");
innerhalb von Loader.load() ?
Ich möchte hier noch einmal wiederholen das ich NICHT am Classpath rumbasteln möchte um das halbwegs zum laufen zu bekommen.
@Tom
Ich bin aber jetzt im gegensatz zu damals bereit mir das mit dem ServiceLoader mal genauer erklären zu lassen.
Also ... wenn irgendwer eine Idee hat warum das hier nicht so läuft wie es mein Gedanke erdacht hat ... immer her damit xD