[Reflection] Klassen aus .class Dateien laden

FBIagent

Erfahrenes Mitglied
Halli hallo,

ich habe ein Problem. Ich möchte in einem Projekt Erweiterungen implementieren die sich selbst über eine
statische Methode registrieren. Als statische Methode setze ich
Java:
public static void initialize();
vorraus. Zum laden der .class Datei verwende ich den URLClassLoader.

Mein Problem besteht darin, dass ich nicht weis wie ich an den genauen namen komme. Ich ging davon aus,
dass ich das Package aus der zu ladenen .class Datei + Klassenname nehmen muss. Allerdings scheint das nicht
der Fall zu sein. Leider konnte ich das Problem bis jetzt mit Hilfe des Internet nicht lösen.

Die Verzeichnissstruktur sieht folgendermaßen aus(namen mit * = verzeichnisse):
Code:
*game
    *extensions
        *tests
            TestBuilderCmd.class
            TestBuilderCmd.java
*lib
    L2DestinyCore.jar
Server.bat - "C:\Program Files (x86)\Java\jdk1.6.0_21\jre\bin\java.exe" -cp "lib/*" de.l2destiny.Server

Server.CUR_DIR beinhaltet den absoluten Pfad des hauptverzeichnisses der Anwendung
Game.CUR_DIR beinhaltet den absoluten Pfad des "game" verzeichnisses

Java:
package de.l2destiny.game;

import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import de.l2destiny.Server;
import de.l2destiny.game.Game;
import de.l2destiny.util.StringUtils;

final class Extensions
{
	private static final String EXTENSIONS_PATH = StringUtils.makePath(Game.CUR_DIR, "extensions");

	public static void load()
		throws Exception
	{
		compile();
		initialize();
	}

	private static void compile()
		throws Exception
	{
		Queue<File> dirs = new LinkedList<File>();
		dirs.add(new File(EXTENSIONS_PATH));

		FileFilter filter = new FileFilter()
		{
			@Override
			public boolean accept(File pathname)
			{
				return pathname.isDirectory() || pathname.getName().endsWith(".java");
			}
		};

		List<File> files = new ArrayList<File>();

		while (!dirs.isEmpty())
		{
			File[] dirEntries = dirs.poll().listFiles(filter);
			for (File dirEntry : dirEntries)
			{
				if (dirEntry.isDirectory())
				{
					dirs.add(dirEntry);
					continue;
				}

				files.add(dirEntry);
			}
		}

	    List<String> options = new ArrayList<String>();
	    options.add("-classpath");
	    options.add(System.getProperty("java.class.path"));

	    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
	    StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
	    Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(files);
	    compiler.getTask(null, fileManager, null, options, null, compilationUnits).call();
	    fileManager.close();
	}

	public static void initialize()
		throws Exception
	{
		Queue<File> dirs = new LinkedList<File>();
		dirs.add(new File(EXTENSIONS_PATH));

		FileFilter filter = new FileFilter()
		{
			@Override
			public boolean accept(File pathname)
			{
				return pathname.isDirectory() || pathname.getName().endsWith(".class");
			}
		};

		List<File> files = new ArrayList<File>();

		while (!dirs.isEmpty())
		{
			File[] dirEntries = dirs.poll().listFiles(filter);
			for (File dirEntry : dirEntries)
			{
				if (dirEntry.isDirectory())
				{
					dirs.add(dirEntry);
					continue;
				}

				files.add(dirEntry);
			}
		}

		for (File file : files)
		{
			ClassLoader loader = new URLClassLoader(new URL[]{file.toURI().toURL()}, Thread.currentThread().getContextClassLoader());
			String filePath = file.getAbsolutePath();
			String classToLoad = filePath.substring(Server.CUR_DIR.length() + 1, filePath.length() - 6).replace(File.separatorChar, '.');
			System.out.println(classToLoad);
			loader.loadClass(classToLoad);
		}
	}
}

Java:
package game.extensions.tests;

public final class TestBuilderCmd
{
	public static void initialize()
	{
		System.out.println("Hello from extensions static TestBuilderCmd.initialize");
	}
}

Könnt ihr mir da weiterhelfen?

Besten Dank im vorraus,
FBIagent
 
Zuletzt bearbeitet:
Das heißt du möchtest wissen, wie der Packagename und der Classname deiner Erweiterungsdatei heißt?
Ich hab mal etwas ähnliches gemacht und war mit Folgendem erfolgreich:
Zum Plugin speichern: .class Datei erstellen oder kopieren, classname / packagename in einer extra Datei (die immer gleich heißt) speichern, und beide Dateien in einen Zipordner packen (diesem evtl. eine andere Dateiendung geben).
Zum Plugin laden: Zip-Ordner entpacken, Classname-Datei lesen, erkannte Klasse laden.
Ich hoffe, das hilft dir weiter. ;)

[EDIT]: Um ein Plugin zu programmieren, solltest du ein Interface oder eine abstrakte Klasse definieren, die deine initialize-Methode enthält und in die du dann deine geladene Klasse castest. Dadurch kannst du diese Methode leicht aufrufen.
 
Zuletzt bearbeitet:
Das heißt du möchtest wissen, wie der Packagename und der Classname deiner Erweiterungsdatei heißt?
Ich hab mal etwas ähnliches gemacht und war mit Folgendem erfolgreich:
Zum Plugin speichern: .class Datei erstellen oder kopieren, classname / packagename in einer extra Datei (die immer gleich heißt) speichern, und beide Dateien in einen Zipordner packen (diesem evtl. eine andere Dateiendung geben).
Zum Plugin laden: Zip-Ordner entpacken, Classname-Datei lesen, erkannte Klasse laden.
Ich hoffe, das hilft dir weiter. ;)

[EDIT]: Um ein Plugin zu programmieren, solltest du ein Interface oder eine abstrakte Klasse definieren, die deine initialize-Methode enthält und in die du dann deine geladene Klasse castest. Dadurch kannst du diese Methode leicht aufrufen.

Wenn ich das mit dem Entpacken mache komm ich doch im Endeffekt auf das gleiche Problem.
In der Datei in der ich den Klassennamen speichere würde ja das gleiche Stehen was ich zur Laufzeit
generiere. In meinem fall "game.extensions.tests.TestBuilderCmd". Die Klasse liegt ja
im Verzeichniss gamem/extensions/tests, somit hat meine Klasse das Package
game.extendsions.tests und URLClassLoader.loadClass rufe ich somit
mit "game.extendsions.tests.TestBuilderCmd" auf. Also ich sehe da jetzt kein
unterschied ob ich diesen Packagestring aus einer extra Datei lade oder ihn generiere, kommt doch
aufs gleiche raus.

Ich hatte mich da glaube ich falsch ausgedrückt. Natürlich weis ich in welchem Package die Klasse liegt. Ich setze
ein Package vorraus, das beim game verzeichniss anfängt. Und der Dateiname ist der Klassenname ohne die
Dateierweiterung.

In meiner Aktuellen Verzeichnissstruktur wird mommentan URLClassLoader.loadClass mit
"game.extensions.tests.TestBuilderCmd" aufgerufen was mir richtig erscheint, allerdings
bekomme ich eine ClassDefNotFound.

Da der Extension loader keine Instanz der Klasse erstellen soll, sondern die Extension sich selbst bei
handler sammlungen registrieren soll macht es da Sinn ein Interface zu verwenden? Das initialize
würde ungefähr so aussehen:
Java:
public static void initialize()
{
    BuilderCmds.register(new TestBuilderCmd());
}

Grüße,
FBIagent
 
Zuletzt bearbeitet:
Dann habe ich die Frage total falsch verstanden. :-(
Kannst du bitte die genaue Ausgabe der ClassDefNotFound-Exception posten?
Wie rufst du denn die initialize-Methode auf? Wird die von loadClass() automatisch aufgerufen?
[EDIT]:
Die initialize-Methode kannst du mit "class.getMethod("initialize", ...).invoke(...);" aufrufen.
 
Zuletzt bearbeitet:
Dann habe ich die Frage total falsch verstanden. :-(
Kannst du bitte die genaue Ausgabe der ClassDefNotFound-Exception posten?
Wie rufst du denn die initialize-Methode auf? Wird die von loadClass() automatisch aufgerufen?
[EDIT]:
Die initialize-Methode kannst du mit "class.getMethod("initialize", ...).invoke(...);" aufrufen.

Die initialize Methode rufe ich momentan noch nicht auf.

Die exception war übrigens ClassNotFoundException.
Bei folgendem code:
Java:
		for (File file : files)
		{
			System.out.println(file.toURI().toURL().toString());
			ClassLoader loader = new URLClassLoader(new URL[]{file.toURI().toURL()}, Thread.currentThread().getContextClassLoader());
			String filePath = file.getAbsolutePath();
			String classToLoad = filePath.substring(Server.CUR_DIR.length() + 1, filePath.length() - 6).replace(File.separatorChar, '.');
			System.out.println(classToLoad);
			loader.loadClass(classToLoad);
		}
bekomme ich folgende ausgabe:

file:/C:/Users/Christian/Documents/Eclipse/workspace/L2DestinyCore/dist/game/extensions/tests/TestBuilderCmd.class
game.extensions.tests.TestBuilderCmd
game.extensions.tests.TestBuilderCmd
java.lang.ClassNotFoundException: game.extensions.tests.TestBuilderCmd
at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
at de.l2destiny.game.Extensions.initialize(Extensions.java:114)
at de.l2destiny.game.Extensions.load(Extensions.java:29)
at de.l2destiny.game.Game.<init>(Game.java:38)
at de.l2destiny.game.Game.getInstance(Game.java:22)
at de.l2destiny.Server.load(Server.java:150)
at de.l2destiny.Server.startup(Server.java:37)
at de.l2destiny.Server.main(Server.java:88)


Working directory der Anwendung ist das dist Verzeichniss.
 
Zuletzt bearbeitet:
In der Zeile, wo du den URLClassLoader initialisiert, ersetze doch mal new URL[]{file.toURI().toURL()} durch den Ordner "dist" als URL, also z.B. durch "new File(Server.CUR_DIR).toURI().toURL()".
Dann sollte es funktionieren. ;)
[EDIT]: Die ganze Zeile soll dann also so lauten:
Java:
ClassLoader loader = new URLClassLoader(new URL[]{new File(Server.CUR_DIR).toURI().toURL()}, Thread.currentThread().getContextClassLoader());
 
Zuletzt bearbeitet:
In der Zeile, wo du den URLClassLoader initialisiert, ersetze doch mal new URL[]{file.toURI().toURL()} durch den Ordner "dist" als URL, also z.B. durch "new File(Server.CUR_DIR).toURI().toURL()".
Dann sollte es funktionieren. ;)
[EDIT]: Die ganze Zeile soll dann also so lauten:
Java:
ClassLoader loader = new URLClassLoader(new URL[]{new File(Server.CUR_DIR).toURI().toURL()}, Thread.currentThread().getContextClassLoader());

Vielen dank, das scheint mein Problem gewesen zu sein.
 
Zurück