Klasse aus .jar updaten

proforma

Mitglied
Hallo,

ich möchte mein Java prog beim Starten updaten. Das Programm ist als .jar zusammengefasst und ausführbar. Kann mir jemand verraten, wie man jetzt eine einzelne Klasse updaten kann?


Danke
 
JAR steht für Java Archiv. Du kannst .jar-Dateien z.B. auch mit winrar öffnen. Du kannst in .jar-Dateien auch einzelne Dateien laden und schreiben. Wenn mich nicht alles täuscht lassen sich einzelne Dateien austauschen indem du so tust als würdest du sie neu anlegen. Die nltigen Klassen dazu findest du hier.
 
Hallo
Dafür gibt es das Paket java.util.jar mit folgenden Klassen:
Attributes
Attributes.Name
JarEntry
JarFile
JarInputStream
JarOutputStream
Manifest
Pack200

Du kannst (analog zu ZipFile/ZipEntry/Zip...) vorgehen. Unter http://download.oracle.com/javase/tutorial/deployment/jar/index.html gibts ein tutorial zu den Jar-Files. Natürlich kannst du Dateien hinzufügen und entfernen. Falls dein Jar-File signiert ist, geht diese Signatur aber vermutlich verloren.
 
Hi,

meinst du mit "updaten beim start" das Hinzufügen eines .class-Files?
Da würde nämlich folgendes Kommando passen:
jar u MyJar.jar MyClassToAdd.class

Gruß
 
Ich glaube TO will etwas ganz anderes, nämlich das hier : http://www.tutorials.de/java/365360-applikation-updaten.html
Wir haben damals dieses Thema so breitgetreten das darin wirklich alles zu finden ist um eine Applikation beim starten zu updaten. Wenn du weitere Fragen zu diesem Thema hast bin ich dir gerne behilflich da ich mitlerweile *dieser Thread ist schon sehr alt* eine etwas verbesserte Variante habe und diese auch gerne zur Verfügung stelle.
 
Hi SpiKEe,

ich bin selber gerade auf der Suche nach Anregungen für einen Updater beim Start einer Anwendung. Hab mir auch den "alten"-Thread durchgelesen.

Da du schreibst, dass du bereits eine verbesserte Variante hast, würde ich gerne mehr über diese Variante erfahren.

Grüße

Sascha
 
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
Java:
File.renameTo(File)
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.
 
Hi SpiKEe,

also so eine ausführliche Antwort hätte ich ehrlich gesagt nicht erwartet :). Klasse****** Großes Lob******

Ich muss mir deinen Beitrag jetzt erst mal durchlesen und dann kann ich ein Feedback abgeben. Ich muss bei mir grundsätzlich 2 verschiedene Update-Varianten unterscheiden. Es gibt Update´s der Anwendung und es gibt sogenannte Datenupdates, die eben die Daten in der Datenbank updaten. Diese Daten kommen meistens als XML, CSV oder Excel in die Anwendung. Hierbei kann ich natürlich keinen HashCode abfragen, sondern muss mit einer Versionisierung arbeiten. Außerdem muss man bei diesen Datenupdates auch auf Fehler reagieren können...kurzes Beispiel...

Anwender hat z.B. die Version 3 der Anwendung auf seinem Rechner und nun zieht er sich ein neues Datenupdate. Das Datenupdate ist für die Version 3 gedacht (Datenmodell sowie Daten aus dem Update müssen zusammen passen). Irgendwo in diesem Datenupdate hat sich ein Fehler eingeschlichen und wir müssen ein neues Update nachschieben. Das neue Update ist auch für die Version 3 gedacht. Hierbei entsteht das Problem, dass mir eine Versionsnummer nicht reicht, also benötige ich darüber hinaus noch eine Versionsnummer innerhalb dieser Updates, also gibt es Update 3_1 und Update 3_2, sprich 2 Versionsnummern.

Die Anwendung selber kann ich ja z.B. nach deinem Schema updaten und so wäre auch mein Vorgehen gewesen, also Update über die Update-Maske runterladen, dann Anwendung neu starten und Update installieren.

Ich werde jetzt mal in deinen Blog gucken :).

Viele Grüße

Sascha
 
Ja sorry,
ich bin gestern einfach nicht mehr dazu gekommen mein System zu bloggen. Werde das im Verlauf des Nachmittages nachholen.

Was du schon richtig angesprochen hast sind die zwei grundlegenden Arten von Updates : Anwendung und Daten
Da sich meine Variante nur auf die Anwendung selbst bezieht kann es sein das es für das Daten-Update nicht funktioniert. Da ich mich selbst aber noch in der Anfangsphase meines großen Projektes befinde *wird noch nicht verraten ... aber zum Public Beta lade ich euch dann noch mal ein wenns soweit ist xD* und jetzt mit Java7 auch einiges leichter umsetzen kann *ich arbeite mit Java7 seit Build43* werde ich bestimmt auch an diesen Punkt stoßen wo ich mir gedanken über die Daten machen muss.

In wie fern du das allerdings mit dem "Fehler" meinst verstehe ich nur halb. Wenn du einen Logik-Fehler meinst : ja sowas passiert ... aber was noch ? Das musst du mir dann doch noch mal erklären.

Was die Versionierung angeht : es ist üblich Punkte oder Begriffe zu nutzen , underscores sind eher unüblich. Auch unterteilt man zwischen verschiedenen Stufen der Versionierung. Das bekannteste Schema ist das hier :

MajorMajorVersion . MajorMinorVersion . MinorMajorVersion . MinorMinorVersion

auch kann man es wie folgt deuten :

Version . Update . Patch . Build

wobei beide Varianten ungefähr gleich oft vertreten sind. Will man es kürzer so lässt man in der Regel die letzte oder die letzten zwei Angaben weg.
Außerdem sieht es nicht schön aus wenn du da mit sowas wie

Version 5.2.17 Update 9.3 Patch 2

ankommst ... da sollte man sich dann doch an die bekannten Konventionen halten. Wobei erlich gesagt habe ich bisher noch nichts gesehen welches noch eine fünfte Unterteilung hat. Ich wüsste auch erlich gesagt nicht wozu man es nun noch weiter spezifizieren sollte als bis runter zum Build.
 
Zurück