Donnerstag, 9. Juli 2009

java_home oder /Library/Java/Home?

Es ist immer wieder eine spannende Frage, wie man in Mac OS X die Standard-Java-Version einstellt bzw. wie man die für eine bestimmte Anwendung "richtige" Java-Version herausbekommt. Mac OS X 10.5 unterstützt derzeit bis zu drei verschiedene Hauptversionen (1.4.2, 1.5.0, 1.6.0), teils als 32-Bit-, teils als 64-Bit-Variante.

Bisher konnten Anwender die bevorzugte Java-Version getrennt für Applets und für andere Anwendungen (Web Start, Kommandozeile) mit /Programme/Dienstprogramme/Java/Java-Einstellungen konfigurieren. Alternativ konnte man natürlich auch die Umgebungsvariable JAVA_HOME setzen oder sich auf den festen Pfad /Library/Java/Home verlassen. Und weil diese Möglichkeiten immer noch nicht bei allen Anwendungen funktionierten, verbogen die ganz Wagemutigen diverse Symlinks im JavaVM-Framework (was aber auch nicht immer klappte und zu diversen Fehlern führte).

Bevor wir uns um die empfohlene Lösung kümmern, hier zunächst eine Warnung: Basteln Sie niemals an den Symlinks in /System/Library/Frameworks/JavaVM.framework (und in Unterordnern davon) herum! Auch dann nicht, wenn Sie viele gutgemeinte Tipps finden, wie man Current und CurrentJDK verbiegen kann... Apple weist seit langem darauf hin, dass diese Symlinks interne Implementationsdetails sind, die sich jederzeit ändern können.

Mit dem aktuellen Java-Update für Mac OS X 10.5 liefert Apple nun ein Kommandozeilenprogramm aus, mit dem sich die Benutzereinstellungen aus /Programme/Dienstprogramme/Java-Einstellungen auslesen lassen (beachten Sie, dass der Java-Unterordner in dem Pfad mit diesem Update verschwunden ist):

HeartOfGold:~ much$ /usr/libexec/java_home
/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home

Ein Aufruf mit der Option -h zeigt alle erlaubten Optionen an. Eine Liste aller verfügbaren JVMs – in der in den Java-Einstellungen festgelegten Reihenfolge – erhalten wir mit --verbose:

HeartOfGold:~ much$ /usr/libexec/java_home --verbose
Matching Java Virtual Machines (4):
1.5.0_19 (i386): /System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home
1.6.0_13 (x86_64): /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home
1.5.0_19 (x86_64): /System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home
1.4.2_21 (i386): /System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Home

Man kann auch ganz gezielt nach einer bestimmten Java-Version und/oder Prozessorarchitektur fragen:

HeartOfGold:~ much$ /usr/libexec/java_home --version 1.6 --arch x86_64
/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home

Sofern die gewünschte JVM nicht vorhanden ist, erhält man eine Fehlermeldung und eine hoffentliche passende alternative JVM:

HeartOfGold:~ much$ /usr/libexec/java_home --version 1.6 --arch i386
Unable to find any JVMs matching architecture "i386".
/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home


Zurück zur Ausgangsfrage. Wie finden wir damit z.B. in einem Shell-Skript die passende Java-Version für eine Anwendung? Apple empfiehlt folgendes Vorgehen:
  1. Prüfen Sie, ob /usr/libexec/java_home vorhanden ist, übergeben Sie optional die gewünschte Java-Version und werten Sie die Rückgabe aus.
  2. Wenn das nicht erfolgreich war, verwenden Sie als Java-Home-Verzeichnis fest kodiert (!) das Verzeichnis /System/Library/Frameworks/JavaVM.framework/Versions/1.X/Home, wobei Sie "1.X" durch die gewünschte Versionsnummer (1.4, 1.5, 1.6) ersetzen.
  3. Sollte das Verzeichnis nicht existieren, verwenden Sie /Library/Java/Home (und beten, das alles klappt).
100% perfekt ist das m.E. zwar nicht, denn Punkt 2 geht schief, wenn das Verzeichnis für Java 6 existiert, Java 6 aber nicht ausgeführt werden kann (das betrifft vor allem 32-Bit CoreDuo-Macs). Aber immerhin hat Apple nun dokumentiert, wie man künftig – und in den meisten Fällen auch bisher – zum korrekten Ergebnis gelangt.


Und was ist mit der Umgebungsvariable JAVA_HOME? Offenbar werten diverse Systemprogramme (wie /usr/bin/java) nicht nur die Rückgabe von /usr/libexec/java_home aus, sondern auch den Wert von JAVA_HOME. Wenn sich die Werte unterscheiden, kann das zu unerwartetem Verhalten führen... Insofern sollte mittelfristig auf das Setzen von JAVA_HOME besser verzichtet werden.

Da sich aber kurzfristig viele Anwendungen noch auf die Umgebungsvariable JAVA_HOME verlassen, ist es derzeit m.E. am besten, die Variable auf die Java-Version zu setzen, die auch in den Java-Einstellungen an oberster Stelle steht (auch wenn das erst einmal doppelten Konfigurationsaufwand bedeutet). Das Setzen der Variablen geschieht am einfachsten mit RCEnvironment, wodurch die Variable sowohl Shell-Skripten als auch GUI-Anwendungen bekannt ist:

Eine ausführliche Diskussion rund um java_home findet sich auf Apples Java-Mailingliste.

Donnerstag, 4. Juni 2009

VisualVM 1.1.1 im JDK

Letzte Woche hat Sun das JDK 6 Update 14 veröffentlicht. Darin enthalten ist unter anderem die aktuelle Version 1.1.1 von VisualVM, dem Werkzeug für Monitoring und einfaches Profiling. Derzeit ist damit also kein separater Download mehr erforderlich, um die vielen kleinen Verbesserungen und Bugfixes gegenüber der Version 1.0.1 zu erhalten, die seit dem JDK 6 Update 11 mitgeliefert wurde.

Sun hat übrigens vor einigen Wochen einen Blog-Wettbewerb zu VisualVM gestartet, der noch bis zum 24. Juni läuft. Dieses Blog läuft allerdings außer Konkurrenz, da es nicht auf Englisch geschrieben ist...

Sonntag, 31. Mai 2009

NetBeans 6.7 im nächsten Monat

Die JavaOne startet in nicht einmal zwei Tagen – ein prima Termin, um NetBeans 6.7 zu veröffentlichen, das sich derzeit noch im Beta-Stadium befindet.

Die neue Version von Suns Entwicklungsumgebung bietet viele Neuerungen und Verbesserungen, u.a. einen erweiterten Profiler, die Unterstützung von GlassFish v3 sowie die Integration von Suns Entwickler-Portal Kenai.

Für Entwickler, die NetBeans unter Mac OS X nutzen, wird dies ein besonders schönes Release. Das Projektfenster wurde an das native Aussehen des Systems angepasst und erinnert nun deutlich an Xcode:

Mittwoch, 29. April 2009

@SuppressWarnings("unchecked")

Seit Version 5.0 kennt Java das Konzept der Generics, mit denen u.a. Kollektionen mit Typen parametrisiert und typsicherer gemacht werden können.

Leider lassen sich damit nicht alle Stellen perfekt absichern – insbesondere bei Hibernate bzw. JPA wird man beim Ausführen von Abfragen (Queries) immer wieder auf "Type safety"-Compiler-Warnungen stoßen:

An sich ist das kein Problem, denn mit der bestehenden API gibt es für den Compiler keine sinnvolle Möglichkeit, vom Query-String auf den Ergebnis-Typ zu schließen. Und Entwicklungsumgebungen wie Eclipse bieten die einfache Lösung an, diese unumgängliche Warnung mit der @SuppressWarnings-Annotation zu unterdrücken.

Nur leider schreibt Eclipse die Annotation immer vor die umgebende Methode, was alle weiteren – vielleicht wichtigen – Typ-Warnungen in der Methode ebenfalls unterdrückt:
@SuppressWarnings("unchecked")
public List<Benutzer> findAllBenutzer() {
Query q = session.createQuery("from Benutzer");
return q.list();
}
Das muss nicht sein! Sehen wir uns dazu den Quelltext der @SuppressWarnings-Annotation an:
@Target({TYPE, FIELD, METHOD, PARAMETER,
CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
LOCAL_VARIABLE bedeutet, dass wir die Annotation auch an eine lokale Variablendeklaration schreiben dürfen. Wenn wir also in der Methode eine temporäre Variable einführen, können wir das Unterdrücken der Warnung auf die eine relevante Zeile beschränken:
public List<Benutzer> findAllBenutzer() {
Query q = session.createQuery("from Benutzer");

@SuppressWarnings("unchecked")
List<Benutzer> alle = q.list();

return alle;
}
Für die JPA-Query-Methoden getResultList() und getSingleResult() gilt dies entsprechend.

Dienstag, 21. April 2009

Oracle kauft Sun

Nun ist es also geschehen: Sun wird verkauft, und nicht etwa an IBM (die Übernahmeverhandlungen waren erst kürzlich gescheitert), sondern an Oracle.

Was dies für die Java-Technologie und die diversen Sun-Java-Produkte konkret bedeutet, bleibt abzuwarten. Immerhin ist Java seit langem gut in bestehende Oracle-Produkte integriert und dürfte für Oracle auch weiterhin von strategischer Bedeutung sein.

Spannend wird es für Suns Application Server GlassFish, der sich seit über einem Jahr deutlich steigender Beliebtheit erfreut. Hier ist zu hoffen, dass Oracle das Potential dieses nun dritten Application Servers im Produktportfolio kennt und nutzt. Berechtigt ist diese Hoffnung allemal, schließlich hat Oracle TopLink Essentials als JPA-Referenzimplementation zu GlassFish beigesteuert.

Mittwoch, 8. April 2009

Serialisierbarer ByteArrayOutputStream

Ein typisches Problem in einer mehrschichtigen Web-Anwendung besteht darin, eine Datei (beispielsweise ein auf dem Server generiertes PDF-Dokument) zum Client/Web-Frontend zu übertragen (oder nach einem Datei-Upload entsprechend umgekehrt). Die einfache Lösung mit Streams schlägt fehl, sobald Sender und Empfänger nicht mehr lokal in derselben Java Virtual Machine laufen, sondern entfernt in verschiedenen JVMs. Die Streams müssten nun entweder entfernt aufrufbar (Remote) oder aber serialisierbar (Serializable) sein – beides sind sie nicht. Die Serialisierbarkeit ist standardmäßig nicht vorgesehen, da Streams geräteabhängig sind.

Eine einfache Lösung für das Versenden kleinerer Binärdateien (wenige hundert KB) macht den Standard-ByteArrayOutputStream serialisierbar, was kein Problem ist, da der ByteArrayOutputStream eine reine Java-Speicher-Implementierung und somit geräteunabhängig ist. Der Sender kann den Stream wie gewohnt mit write() füllen, und der Empfänger kann den Stream mit den üblichen Methoden auswerten. In einem Servlet kann man den Stream z.B. mit baos.writeTo(response.getOutputStream()) direkt zum Client schicken.

Für die Implementierung des serialisierbaren ByteArrayOutputStreams reicht es nicht aus, die neue Unterklasse das Serializable-Interface implementieren zu lassen. Man muss zusätzlich noch die Methoden writeObject() und readObject() implementieren, die den internen Stream-Puffer explizit serialisieren:

public class SerializableByteArrayOutputStream
extends ByteArrayOutputStream
implements Serializable {

private void writeObject(ObjectOutputStream stream)
throws IOException {
stream.writeInt( count );
stream.writeObject( buf );
}

private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
count = stream.readInt();
buf = (byte[]) stream.readObject();
}
}

Der vollständige Quelltext der Klasse
SerializableByteArrayOutputStream kann hier abgerufen werden.

Wer eine umfassendere Lösung sucht oder große Dateien übertragen muss, sollte sich die RMIIO-Bibliothek anschauen.

Mittwoch, 1. April 2009

Proxy-Konfiguration für VisualVM

Wenn man mit VisualVM in einem Netzwerk arbeitet, das über einen Proxy ans Internet angebunden ist, hat VisualVM Probleme mit dem Nachladen seiner Plugins – was schade ist, denn dadurch verschenkt man die Hälfte der Möglichkeiten.

Der Proxy kann leider nicht in VisualVM selbst konfiguriert werden, sondern muss mit Aufrufparametern übergeben werden:

visualvm -J-DproxyHost=mein.proxy.com -J-DproxyPort=1234

Aber Achtung: Dies sind nicht die üblichen Java-Proxy-Parameter http.proxyHost und http.proxyPort! Das "http." fehlt jeweils.

Außerdem scheint die Konfiguration mit dem im JDK 6 mitgelieferten VisualVM nicht zu funktionieren. Mit dem separat herunterladbaren VisualVM 1.1.1 klappt es aber wie gewünscht.