Update einer Jar während der Laufzeit

Xeonkryptos

Bekanntes Mitglied
Ich weiß, dass das Thema schon oft angesprochen und erklärt wurde und die SF von dem Forum, sowie google hab ich schon verwendet, aber dennoch komme ich nicht weiter...

Ich habe ein Programm, welches ich natürlich ab und an mal eine Aktualisierung unterziehe, ich aber keine Lust habe, meinen Bekannten/Freunden, die das Programm nutzen, immer wieder die neue jar-Datei zu schicken. Nun hätte ich gerne eine Update-Funktion geschrieben, wodurch ich das Programm während der Laufzeit aktualisiere und alle neuen Funktionen OHNE einen Neustart nutzbar machen möchte. Das dieses geht, ist mir bewusst, aber ich hab das Problem in der Umsetzung.
Ich lasse mein Programm auf einen Server zugreifen auf dem sich eine Textdatei befindet, worin steht, welche Versionsnummer momentan die aktuellste ist und die mit der vom Programm verglichen wird, ist die Versionsnummer in der txt-Datei höher, verbindet das Programm sich mit einem Server und lädt die aktuellste Datei herunter (die aktuelle Datei liegt im selben Pfad wie der Server). Das funktioniert soweit ganz gut. Diese landet dann auch ohne Probleme auf der Platte des Rechners. (Ist eine jar-Datei die geladen wird!)

Nun möchte ich die Klassen aus dem neuen jar in die momentan laufende Anwendung einbauen/laden und die neuen Funktionen nutzbar machen. Auch soll dann zum Beispiel die Versionsnummer erhöht werden, die sich in einer Config-Klasse befindet, aktualisiert werden.

Nun habe ich, wie schon oben erwähnt, viel recherchiert und weiß, dass ich die Klassen neu laden muss, am besten über einen eigenen Classloader bzw den URLClassLoader, um die alten zu ersetzen. Nun leitet der URLClassLoader die Anfrage erst an den ClassLoader, der schon meine Anwendung geladen hat weiter und es wird die Klasse nicht neu geladen. Mein Ziel ist es, dieselben Klassen mit der selben Package-Struktur aus der neuen Jar zu laden und die alten zu "ignorieren", damit der ClassLoader vom GC weggeräumt werden kann, doch kriege ich das nicht hin.

Mein Ansatz:
Java:
ClassLoader loader = null;
		try {
			loader = URLClassLoader.newInstance(new URL[] { new File(
					Config.FILE_PLACE2).toURI().toURL() });
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		Class<?> c = null;
		try {
			c = loader.loadClass("client.core.Main");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		try {
			c.newInstance();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}

Hier habe ich es testweise erstmal mit einer einzigen Klasse versucht, die auch gefunden wird, aber nicht geladen wird, da diese schon im Parent-ClassLoader vorhanden ist und ich diesen leider nicht umgehen kann, zumindest mir nicht bewusst wie.
Es hieß immer, ich müsse einen neuen ClassLoader instanzieren und die neuen Klassen laden, sodass dadurch im Endeffekt die Referenz zum anderen ClassLoader "verloren" geht und der GC diesen wegräumt. Auch hieß es überall, dass die neu geladenen Klassen durch einen neuen ClassLoader einzigartig sind, dass heißt, dass diese auch im Parent-ClassLoader vorhanden sein können, ohne das Probleme auftreten.
Genau hier fängt mein Problem an. Ich müsste den Parent-ClassLoader umgehen oder zumindest meinen eigenen ClassLoader trotz der schon geladenen Klasse die Klasse neu laden können.

Weiterhin würde ich dann gerne wissen, wie ich es am schönsten schaffe, wenn alle Klassen neu geladen sind, OHNE ein Neustart der JVM, wie ich die GUI dann darauf aufmerksam mache, dass es sich mal aktualisieren/anpassen soll. Mir ist die Methode validate() bekannt, nur hat die neu geladene Klasse denn auch die gleiche Referenz auf dasselbe schon offene Fenster? Ich meine mal nicht.

Zumindest ist mir der obere Teil erstmal wichtiger und ich hoffe, ihr könnt mir helfen.
 
Hallo Xeonkryptos,
Du hast Dich wahrscheinlich schon intensiv mit dem Thema auseinandergesetzt. Ich nicht so 😳 Aber vielleicht hilft Dir der Hinweis auf die Methode newInstance, bei der man den parent class loader als Parameter mitgeben kann. Der kann sogar null sein. Damit hast Du einen, der nicht zu dem delegiert, der Deine Klassen schon geladen hat. Ich habe mal etwas Testcode geschrieben und es damit hinbekommen, mehrere Male die selbe Klasse zu laden:
Java:
package test;

import java.net.URL;
import java.net.URLClassLoader;

public class Test {

	public static void main(final String[] args ) throws Exception {
		
		final URL[] urls = { new URL("file:///home/nille/projects/TestProject/bin/") };

		final ClassLoader withParent = URLClassLoader.newInstance(urls);

		final Class<?> firstWith = withParent.loadClass("test.Test");
		
		System.out.println(firstWith==Test.class);
		
		final ClassLoader withoutParent = URLClassLoader.newInstance(urls, null);
		
		final Class<?> firstWithout = withoutParent.loadClass("test.Test");
		
		final Class<?> secondWithout = withoutParent.loadClass("test.Test");
		
		System.out.println(firstWith == firstWithout);
		
		System.out.println(firstWithout == secondWithout);
	}
}
Pfadnamen, Packages und Klassen musst Du natürich anpassen. Aber das Prinzip dürfte klar sein.
 
Danke für den Hinweis, das werde ich mal in angriff nehmen... Darauf hätte ich auch selber kommen können, aber wie immer sehe ich die Feinheiten immer dann, wenn ich schon längst am verzweifeln bin. 😀

Das kommt davon, wenn man sich auf den ClassLoader fixiert und sich nur auf die eine Methode verharrt. =) ... schlimm ist aber auch, dass es überall heißt, man soll genau das machen, wie ich beschrieben hab, diese aber in Codebeispielen immer die einfache newInstance() verwenden. 🙁
 
Okay... jetzt hab ich meinen Fehler eingesehen^^ Ich hab außer acht gelassen, dass ich den parent-classloader auch mit null beziffern kann. 🙂 😳

Edit: Jetzt hab ich aber noch die offene Fragen, wie schon oben erwähnt: Wie schaffe ich das nun, dass das Frame mit aktualisiert wird, nachdem ich alle Klassen neu geladen hab bzw die Frame-Klasse?

Für mich besteht das Problem, dass das Frame-Objekt nicht die neu geladene Klasse kennt bzw das Objekt in der Klasse nicht die Referenz zu dieser Klasse besitzt. Mit validate() komme ich hier nicht weiter, wenn ich richtig liegen sollte. Muss ich das Frame erst disposen und dann mit der neuen Klasse instanzieren oder gibt es einen schöneren Weg? Da ich das alles in einem Chat verwende, sollte der Verlauf jetzt nicht gelöscht werden oder andere Dinge, da ich diese noch nicht auf die Festplatte schreiben lassen möchte.
 
Zuletzt bearbeitet:
Du erfindest das Rad zum x-ten mal neu.

Es gibt schon viele Classloader Implementierungen, die so etwas können. Ein gutes Beispiel ist der ANT Classloader, der mit isolierten Klassenpfaden und parent-last delegation konfiguriert werden kann. Das Thema ist alles andere als trivial und der Teufel steckt im Detail, z.B. was machst du mit alten Intanzen der Klassen, die plötzlich ersetzt werden? Passen die immer mit den neuen Klassen zusammen?
An diesen Themen haben sich große Tools und APIs lange Zeit die Zähne ausgebissen.

Ich habe mal gelesen, dass JBoss seine Classloading in eine API gegossen hat, die man auch in eigenen Applikationen verwenden kann. Guck mal danach (evlt. 9.2.*ClassLoading ).

Frage: warum kann man nicht einfach einen Neustart machen, wenn die neue Version heruntergeladen wurde? Damit entledigst du dich aller Probleme...

... oder nimm OSGi...
 
Ich sehe ein, dass es irgendwann zu schwer werden wird. Zu Anfang kann ich noch sagen, dass andere Klassen alle die neuen Klassen verwenden können, weil ich veränderte Klassen einfach neu geladen hätte, aber irgendwann wäre das wohl zu viel geworden, weil ich das auch erweitern möchte.
Ich versuche mich jetzt mit dem OSGi-Framework und möchte da gleich mal fragen, ob es da irgendein Tutorial gibt, welches ihr als gut empfunden habt und weiterempfehlen könnt? Für einen Einstieg mit dem Framework.

@FArt: Ich möchte einen Neustart verhindern, weil ich das ansonsten auf ein Script oder ein anderen Programm, welches nicht in Java geschrieben ist, auslagern müsste. Wieso soll man auch die Möglichkeit außen vor lassen mit dem Neuladen von Klassen und diese benutzen (Plug-In-Verhalten), wenn mir Java dieses bietet und ich daran lernen kann? Es mag zwar komplizierter sein, aber irgendwo muss es auch Herausforderungen geben. 🙂
Außerdem ist das Neustarten ein nerviges Unterfangen mit seinen Vorteilen, ja, aber wenn ich es umgehen kann, wieso nicht? Ich verlange ja keinen fertigen Code ohne, dass ich irgendeinen Ansatz gebracht habe!
 
Hi,

schau dir mal Java Web Start an, dass müsste genau das richtige für dein Problem sein. Damit kann sich dein Programm während des Startens zu einem Server verbinden und prüfen, ob eine Aktualisierung durchzuführen ist. Weiterhin kannst du es für die Verbreitung deines Programmes wunderbar nutzen, weil zur Installation lediglich ein Link angeklickt werden muss. Alles weitere passiert dann automatisch.

Grüße,
Andreas
 
Ich bin mir bewusst, dass es WebStart gibt, ich möchte dieses aber erstmal nicht nutzen. Mein Programm dient mir auch zum Üben und da mach ich so viel ich kann lieber selbst. 🙂
 
Neustart ist der beste weg, der user kennt das von anderen programmen. Notfalls sagen "programm muss neugestartet werden...."
 

Neue Themen


Zurück
Oben