Mittwoch, 22. Juli 2009

Lokaler Aufruf von @Remote in GlassFish

GlassFish muss als Java EE 5-konformer Application-Server beim Aufruf von EJB-@Remote-Interfaces die Übergabeparameter und Rückgabewerte kopieren (serialisieren). Der Aufruf erfolgt also mit Wert-Semantik ("pass-by-value"), selbst wenn der Aufrufer und die aufgerufene Session-Bean innerhalb derselben JVM laufen ("co-located") und der Aufruf damit prinzipiell lokal erfolgen könnte (durch Über-/Rückgabe von Referenzen, entsprechend "pass-by-reference" genannt).

Obwohl GlassFish den eigentlichen Remote-Aufruf bereits gut optimiert, wenn sich Aufrufer und aufgerufene Bean auf demselben Server befinden, kostet das Kopieren der Parameter Zeit und Speicher. Wenn die Parameter große Datenmengen beinhalten, kann es sich daher als Performanz-Optimierung lohnen, die Parameter des lokalen Remote-Aufrufs entgegen der Spezifikation als Referenz übergeben zu lassen. Man muss dann aber bedenken, dass Änderungen an den Parameterdaten – wie bei @Local-Aufrufen – an allen Stellen sichtbar sind, die eine Referenz darauf halten.

Aktivieren lässt sich der lokale Aufruf von @Remote-Methoden in GlassFish für eine komplette Enterprise-Applikation (EAR) im sun-application.xml-Deskriptor wie folgt:
<sun-application>
<pass-by-reference>true</pass-by-reference>
</sun-application>
Oder für einzelne EJBs in sun-ejb-jar.xml:
<sun-ejb-jar>
<enterprise-beans>
<ejb>
<pass-by-reference>true</pass-by-reference>
</ejb>
</enterprise-beans>
</sun-ejb-jar>
Achtung: Zumindest GlassFish 2.1 scheint einen Bug zu haben, dass die Einstellung in sun-application.xml ignoriert wird, wenn sich in einem EJB-Modul (EJB-JAR) kein sun-ejb-jar.xml befindet. Abhilfe: Einfach einen (abgesehen vom Wurzel-Element) leeren Deskriptor ergänzen.

Und nochmal Achtung: Offenbar klappt die Optimierung nur innerhalb einer Anwendung (EAR), nicht aber zwischen mehreren separaten Anwendungen bzw. EJB-Modulen auf demselben Server.

Warum will man überhaupt eine nicht standardkonforme Referenz-Übergabe aktivieren? Kann man nicht besser @Local-Interfaces verwenden, die die erwähnten Performanz-Vorteile sowieso mit sich bringen? Ja, man sollte @Local-Interfaces nach Möglichkeit bevorzugen.
Es gibt aber Fälle, bei denen man nicht schon bei der Programmierung entscheiden kann bzw. möchte, wie die Bean-Aufrufe durchgeführt werden, beispielsweise weil man sich die Option offen halten möchte, ob Frontend (WAR) und Backend (EJB-JAR) gemeinsam in einem EAR oder aber separat bereitgestellt ("deployed") werden. Im Quelltext wird man dann die @Remote-Variante programmieren, hat aber mit dem pass-by-reference-Schalter später per Konfiguration die Möglichkeit, die Aufrufart einfach und schnell umzustellen.

Sinnvoll erscheint dies vor allem dann, wenn man viele EJBs entfernt aufruft und alle Aufrufe auf einen Schlag optimieren will. Wenn man dagegen nur wenige EJBs entfernt nutzt oder nur einzelne Aufrufe optimieren möchte, kann man die Aufrufart – wenn auch mit etwas mehr Aufwand – über die Standard-XML-Deskriptoren ändern. Eine mögliche Realisierung ist hier beschrieben.

Donnerstag, 16. Juli 2009

Sein oder !sein, das ist hier die Frage

Echte C-Freaks werden mich dafür verachten, aber ich finde ausgeschriebene boolesche Operatoren wie not, and und or einfach lesbarer als !, && und || (und ja, ich habe jahrelang C und C++ programmiert und weiß, wovon ich rede). Ein Beispiel:
  if (!liste.isEmpty()) { ... }

if (not(liste.isEmpty())) { ... }
M.E. ist das Ausrufungszeichen beim Durchsehen des Quelltextes viel zu leicht zu übersehen (und Code wird normalerweise viel häufiger gelesen als geschrieben).

Während beispielsweise die EL beide Schreibweisen erlaubt, kennt Java leider nur die Symbol-Operatoren. Was tun? Zumindest für den Präfix-Operator not kann man statische Methoden definieren, die sich dann mittels statischer Imports wie in obigem Beispiel nutzen lassen:
  public static boolean not(boolean bval) {
return !bval;
}

public static Boolean not(Boolean bval) {
if (bval == null) {
return null;
}
else {
return !bval;
}
}
(Den kompletten Quelltext gibt es hier.)

Nur: Muss man sich diese Methoden selber schreiben, oder gibt es dafür bereits fertige Bibliotheken? Im Netz habe ich leider nichts passendes gefunden.

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.