Hey MS-Tech,
kein Problem ... ich werde diese heute noch in meinem Blog veröffentlichen.
Um dir allerdings hier und jetzt schon mal schnell ne Einführung zu geben wie das ganze dann genau Funktioniert beschreib ichs mal schnell :
Beim Starten deiner App startest du nicht direkt deine Hauptklasse sondern einen vorgeschalteten Launcher. Daher kann
Java:
public static void main(String[])
aus deiner Hauptklasse verschwinden da diese über den Launcher gestaret wird *WIE weiter unten*.
Der Launcher kann auch im selben Paket oder sogar im selben JAR liegen wie deine eigentliche Hauptklasse.
Wird nun dein JAR gestartet wird erst der Launcher gestartet. Wichtig hierführ ist das
Java:
public static void main(String[])
aus deiner Hauptklasse in den Launcher wandert. Auch muss der Main-Class-Eintrag im Manifest geändert werden.
Der Launcher prüft bei einem dazu passenden Server *kann ich dir gerne auch zur Verfügung stellen wenn du möchtest* oder das aktuelle JAR überhaupt noch aktuell / gültig ist.
Das kannst du entweder mit einer Versionsnummer im Launcher machen *was aber *aus Erfahrung* eher schlecht ist da wenn du es mal vergisst dieser Nummer zu ändern eine Endlos-Schleife entsteht*.
Oder über einen Hash-Wert des JAR-Files.
Hier sind Hashverfahren nach dem SHA-2 Standard *SHA-256 , SHA-512* zu empfehlen da MD5 und SHA-1 sehr schnell zu kollisionen führen KÖNNEN *hier mal nicht als Brute-Force Angriff sondern als Zufälliger Treffer gesehen*. SHA-256 bietet hier schon eine sehr gute Sicherheit bei kleinem Aufwand. Außerdem wird SHA-256 vom SUN -Provider unterstützt. Du brauchst also noch nicht mal eine extra LIB dazu.
Nun baut der Launcher eine Verbindung zum Update-Server auf und Fragt nach für das JAR-File "abc.jar" der Hash "aabbccddeeff" noch aktuell ist. Der Update-Server beantwortet diese Frage entweder durch "wissen" *weil er beim Start das aktuelle JAR-File eingelesen und gehashed hat *sehr schelcht da bei jedem Update der Update-Server neu gestartet werden muss** oder Fragt eine Datenbank nach den nötigen Infos. *Meine Variante bezieht sich auf eine bereits lauffähige MySQL-Datenbank mit entsprechendem User , Rechten und Tabellen.*
Nun sendet der Server an den Clienten ein Kommando in dem entweder "HASH OK" / "FILE UP-TO-DATE" oder eben "HASH FAIL" / "FILE OUT-OF-DATE" steht.
Wenn der Hash OK ist dann läd der Launcher die Hauptklasse einfach in dem ein neues Objekt dieser Klasse erzeugt wird. Desshalb brauchst du bei meiner Variante auch mindestens einen
public Konstruktor deiner Hauptklasse. *Wenn der Launcher im selben "package" wie die Hauptklasse liegt reicht auch ein
protected Konstruktor.*
Wenn vom Server allerdings ein FAIL kommt fragt der Launcher nun nach den aktuellen Daten. Da sich der Hash auf das gesamte JAR bezieht sollte auch das komplette JAR heruntergeladen werden. Das macht sich am einfachsten wenn du mit binären Streams *also den ganz einfachen InputStream / OutputStream* arbeitest. Wenn du mit String-basierten Streams arbeiten willst musst du das JAR vor dem Senden natürlich BASE64 kodieren und nach dem empfangen wieder dekodieren. Da BASE64 aber im package sun.* liegt würde ich dir von dessen Verwendung abraten *wenn unbedingt BASE64 dann selbst implementieren oder eine Lib von einem Drittanbieter nutzen*.
Die Daten des "neuen" JAR werden nun als "abc.new" gespeichert.
Damit hast du erstmal das neue JAR auf dem Rechner.
Was musst du jetzt aber machen um das alte gegen das neue zu tauschen ?
Nun ... wie du ja in meinem Post gelesen hast geht es nicht einfach so da die aktuelle Instanz der JVM auf das aktuelle "abc.jar" zugreift und es "gesperrt" hält. Um das jetzt zu ändern musst die die JVM beenden. Aber dann bist du ja raus aus deiner App ... und nun ?
Tja ... um das zu verhindern musst du vor dem Beenden der aktuellen JVM eine neue Instanz starten. Da es nicht einfach reicht eine 3te Klasse zu nehmen musst du einen komplett neuen Prozess anstoßen. Der Einfachheit halber habe ich das mit einem simplen
Java:
Runtime.getRuntime().exec()
gelöst. Dieser Aufruf veranlasst das System *mit entsprechendem String als Übergabewert* eine komplett neue Instanz der JVM zu starten welche von deiner ursprünglichen unabhängig ist.
Nun kannst du beruhigt aus der ursprünglichen JVM mit System.exit(int) aussteigen.
Was macht die neue JVM jetzt ?
Nun ... natürlich startest du nicht einfach eine neue JVM mit dem neuen JAR als Parameter. Stattdessen wird eine Helper-Klasse verwendet. Diese Helper-Klasse hat nichts weiter zu tun als mit einem
Java:
File file=new File("abc.jar");
while(!file.delete()) { }
darauf zu warten das die alte JVM endlich aussteigt und die Sperrung des JAR aufhebt. So bald das alte JAR gelöscht ist kannst du nun mit der Methode
das neue JAR "abc.new" in "abc.jar" umbennen.
Nun startest du aus dieser Helper-Klasse ebenfalls eine neue JVM-Instanz mit "abc.jar" als Parameter. Das ganze läuft nun wieder von vorne ab ...
mit einem Unterschied : der Server meldet jetzt das der Hash OK ist ... und der Launcher fährt mit dem Laden der Hauptklasse fort.
Ich weis ... so ausführlich beschrieben hört sich das ganze unglaublich kompliziert an ... ist es aber nicht. Im Gegenteil : es ist sogar so einfach das du es sogar mit normalen HTTP-Server machen kannst. Alles was du dafür brauchst ist nur ein wenig Einarbeitung in HTTP oder nutzt die Apache Commons Lib dafür *sehr beliebt hier im Forum ... daher auch viele die dir dabei helfen können*.
Als Anmerkung : wenn du an deine App Parameter übergibst kannst du diese "durchreichen" in dem die die Helper-Klasse so umschreibst das sie die von der ersten JVM erhaltenen Parameter an die neue übergibt. Das ist nur jeweils eine Zeile Code sowohl im Launcher als auch in der Helper-Klasse.
Wie gesagt : ich werds nachher noch alles schön zusammen stricken und dann in meinem Blog veröffentlichen. Ich werde auch gleich die Server-Klassen dazulegen.