JAR-Datei nachträglich austauschen

Moltar

Mitglied
Hallo,

ich brauche für eine Anwendung die Möglichkeit, zur Laufzeit einzelnen Komponenten auszuwechseln. Das soll so funktionieren, dass ich jeweils die verwendeten JAR-Dateien auswechsle.

Die beiden Themen haben mir schon recht weit geholfen:
http://www.tutorials.de/forum/java/224044-aktuelle-jar-datei-nutzen.html

http://www.tutorials.de/forum/java/153725-classpath-dynamisch-setzen.html

Allerdings habe ich noch das Problem, dass das JAR-File unter Windows gesperrt ist. D.h. ich kann es weder löschen noch ersetzen.

Hier meine Klasse:
Java:
public class ClassManager {
private final HashMap<String, Class<?>> classes;
public ClassManager() {
		classes = new HashMap<String, Class<?>>();
	}

public final void loadClass(String classpath, String classname) throws MalformedURLException, ClassNotFoundException {
		if(classes.containsKey(classname)) {
			return;
		}
		URL url = new File(classpath).toURI().toURL();
		URLClassLoader ucl = new URLClassLoader(new URL[] {url});
		Class<?> c = ucl.loadClass(classname);
		classes.put(classname, c);
	}

public final void unloadClass(String classname) {
		if(!classes.containsKey(classname)) {
			return;
		}
		classes.remove(classname);
	}

public final <T> T getObject(String classname) throws InstantiationException, IllegalAccessException {
		if(!classes.containsKey(classname)) {
			return null;
		}
		return (T) classes.get(classname).newInstance();
	}
}

Und hier der Testcode:
Java:
public class ClassManagerTest {
	public static void main(String[] args) throws IOException {
		run();
		System.in.read();
	}

	public static void run() {
		try {
			ClassManager manager = new ClassManager();
			String jar = "..."; // Gültiger Pfad zu meinem Jar			String cn = "..."; // Klassennamen inkl. packages
			manager.loadClass(jar, cn);
			ClassManagerTestModuleInterface m = manager.getObject(cn);
			System.out.println("MODULE: " + m.getString());
			m = null;
			manager.unloadClass(cn);
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
}

Die geladene Klasse implementiert das Interface ClassManagerTestModuleInterface und stellt eine Methode getString zur Verfügung, die einen Teststring zurückgibt.

Das funktioniert soweit alles. Nun will ich aber während er auf die Eingabe wartet, das JAR austauschen und das geht nicht.

Bin für jeden Hinweis dankbar.
Moltar
 
Hallo,

wenn du jars zur Laufzeit austauschen können möchtest musst du diese über einen eigenen ClassLoader laden.
So könnte man beispielsweise beim Start der Anwendung die "austauschbaren" (quell) Jars in ein temporäres Verzeichnis kopieren und diese dann via URLCLassLoader von dort aus verwenden. So vermeidet man das die "quell jars" gelocked werden. Zusätzlich starten man nun sowas wie einen FileSystemWatcher Thread der die "quell jars" auf manipulation hin untersucht (ändern, löschen etc.) wurde eine solche Modifikation erkannt kann der entsprechende Programmteil davon in Kenntnis gesetzt werden was dann beispielsweise zu Deaktivierung von entsprechenden Programmteilen führt (und wegwerfen des bisland verwendeten ClassLoaders). Anschließend wird das modifizierte "quell jar" wieder in ein temporäres Verzeichnis kopiert und dann wie zuvor von einem neuen URLCLassLoader geladen.

Weiterhin kannst du eine gesperrte Datei zwar nicht löschen, wohl aber überschreiben (was sich in dem Fall jedoch auch erst auswirkt, wenn der entsprechende ClassLoader nicht mehr verwendet wird.)

Java:
/**
 * 
 */
package de.tutorials;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.TimeUnit;

/**
 * @author Thomas.Darimont
 * 
 */
public class RefreshJarExample {

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
		URLClassLoader classLoader = new URLClassLoader(new URL[] { new File(
				"c:/temp/xxx/service.jar").toURI().toURL() });

		IService service = IService.class.cast(classLoader.loadClass(
				"de.tutorials.Service").newInstance());
		
		service.operation();
		
		System.out.println("Sleeping...");
		TimeUnit.SECONDS.sleep(15);
		
		classLoader = new URLClassLoader(new URL[] { new File(
		"c:/temp/xxx/service.jar").toURI().toURL() });
		
		service = IService.class.cast(classLoader.loadClass(
		"de.tutorials.Service").newInstance());
		
		service.operation();

	}

}

Gruß Tom
 

Anhänge

Hallo Thomas,

erstmal danke für Deine Hilfe. Mir ist allerdings noch nicht so ganz klar, was Du in Deinem Code anders machst, als ich.

Du erzeugst zweimal je einen URLClassLoader, der lädt das JAR, lädt die Klasse, erzeugt eine Instanz und ruft die Methode auf. Aber ist das nicht das gleiche, was ich bei mir durch die Methodenaufrufe mache?

Bei mir wird ja bei jedem Aufruf von loadClass auch ein neuer URLClassLoader für das konkrete JAR und die konkrete Klasse angelegt. Der ClassLoader müsste dann ja am Ende der Methode automatisch entsorgt werden. So wie bei Dir, wenn Du ihn überschreibst.
Oder muss ich den ClassLoader explizit überschreiben?

EDIT: Ich habe mal noch ein bisschen getestet. Das überschreiben des alten JAR funktioniert, wenn ich das neue in den Zielordner kopiere. Aber es schlägt fehl, wenn ich das neue in den Zielordner verschiebe. Damit ist mein Problem gelöst. Vielen Dank :)

Grüße
Moltar
 
Zuletzt bearbeitet:
Zurück