Zur Zeit wird gefiltert nach: Roman Seibold
Filter zurücksetzen
Die 80%-Schleife

- Roman Seibold, Senior Consultant und technischer Leiter der Schulungsabteilung von aformatik
von Roman Seibold
Natürlich ist sie schon ein wenig älter, die "neue" for-Schleife, oder auch "enhanced-for-loop", sogar schon über fünf Jahre, aber sie lohnt dennoch einen genaueren Blick. Für alle, die sie noch nicht kennen sollten: seit Java 5 kann man mit einer vereinfachten Syntax über Arrays oder Collections iterieren. Beispiel:
String[] array = { "enhanced", "for", "loop" };
for (String string : array)
{
System.out.println(string);
}
Eingebürgert hat sich auch ihr zweiter Name "for-each-loop", da die Lesart des obigen Konstrukts wie folgt lautet: "for each String string in array do..."
Hier stellt sich dem einen oder anderen schon die erste Frage: warum heisst das Schlüsselwort dann nicht "foreach" und warum muss ich einen Doppelpunkt machen, wenn man hier "in" liest? Diese Frage haben die Macher der Spezifikation (der JSR 201, der im übrigen auch Autoboxing, Enums, Varargs und static Imports enthält) gleich mit der Spezifikation beantwortet: "Die Kosten, neue Schlüsselwörter in der Sprache zu verankern, sind enorm. Außerdem entsteht eine Inkompatibilität zu existierendem Sourcecode, der die neuen Schlüsselwörter als Identifier benützt. Ganz konkret würde beispielsweise das Schlüsselwort 'in' stark in Konflikt mit 'System.in' geraten."
Ähnliches hat man ja auch erlebt, nachdem das Schlüsselwort 'enum' in Java 5 eingeführt wurde, da hat so manche Schleife, die mit einer Enumeration gearbeitet hatte, nicht mehr compiliert. Vermutlich wollte man uns mehr solcher Dinge ersparen.
Eine zweite Frage kommt einem in den Sinn: schön, dass ich nun über Arrays iterieren kann, aber warum kann ich dabei keine Änderungen vornehmen? Zur Illustration:
String[] array = { "enhanced", "for", "loop" };
for (String string : array)
{
string = "changed";
}
Das compiliert zwar, erfüllt aber nicht den beabsichtigten Zweck überall im Array den String "changed" zu hinterlegen. Für Schreibzugriffe auf Collections und Arrays muss man nach wie vor mit einer klassischen for- oder while-Schleife arbeiten. Auch hier hat die Spezifikation schon wieder eine gute Begründung parat: "Die Expertengruppe hat über solche Fälle nachgedacht, dann aber für eine einfache und saubere Erweiterung gestimmt, die eine Sache macht, und die gut. Das Design folgt der 80-20-Regel (d.h. es löst 80% der Fälle mit 20% Aufwand). Wenn ein Problem in die restlichen 20% fällt, kann man es immer noch mit einem expliziten Iterator oder einem Index lösen, genau so wie man es in der Vergangenheit getan hat."
Jetzt, nach über 5 Jahren, kann man zurückblickend sagen, dass diese Entscheidung gut war. In vielen, vielen Fällen möchte man nur über Werte iterieren und mit diesen Werten dann eine andere Funktionalität aufrufen. Und das sind gefühlt schon diese 80%.
Und noch eine Frage mag sich stellen: wo ist denn eigentlich bei Collections mein Iterator hin? Das Beispiel von oben, in Collections übersetzt:
String[] array = { "enhanced", "for", "loop" };
List<string> list = Arrays.asList(array);
for (String string : list)
{
System.out.println(string);
}
Hier taucht überhaupt kein Iterator auf. Die Lösung ist, dass in Wirklichkeit die enhanced-for-loop gar nicht mit Collections arbeitet, sondern nur mit Arrays und Instanzen von java.lang.Iterable. Iterable wiederum ist ein Interface, dass nur eine einzige Methode enthält, nämlich iterator(). In Java 5 wurde also die Eigenschaft, einen Iterator produzieren zu können aus dem Collection-Framework herausgelöst. Direkt mit einem Iterator arbeitet die neue for-Schleife auch überhaupt nicht, d.h.
Iterator<string> iterator = list.iterator();
for (String string : iterator)
{
System.out.println(string);
}
funktioniert nicht. Warum denn das nicht? Hier gibt es ebenfalls eine Erklärung, die sich in der Spezifikation findet. "Es gibt zwei Gründe, warum die foreach- Schleife nicht mit einem Iterator funktioniert: (1) Das Konstrukt würde nicht mehr wirklich viel syntaktische Verbesserung bringen und (2) die Ausführung der Schleife hätte den Seiteneffekt, dass sie den Iterator verbraucht."
Außerdem ist der Iterator ja genau das Artefakt in einer solchen Schleife, an dem man am wenigsten interessiert ist. Genaugenommen braucht man ihn ja nur für den Zweck, die Collection besuchen zu können, wie man die oft bemühte Indexvariable i häufig nur braucht, um auf ein Array in einer Schleife zugreifen zu können. Mit dem Iterator oder mit i selbst möchte man (in 80% der Fälle) eigentlich gar nichts machen, sie sind also nur unnötige technische Artefakte. Man vergleiche selbst:
// 1
for (String string : array)
{
System.out.println(string);
}
// 2
for (int i = 0; i < array.length; i++)
{
System.out.println(array[i]);
}
Im zweiten Beispiel muss man einiges tun. Man braucht eine Hilfsvariable i vom Typ int. Man muß wissen, dass Arrays ab 0 zählen. Man muss die Obergrenze definieren. Man darf nicht vergessen, i in jedem Schleifenschritt zu erhöhen. Und man muss den Elementzugriff mit dem Indexoperator [] durchführen. Klar, kalter Kaffee für erfahrene Java-Leute. Aber zugegeben, der Code mit der Hilfsvariablen ist mehr und auch etwas schwieriger zu lesen. Und vom Code-Verständnis bis zur Wartung ist nur noch ein kleiner Schritt.
Wer übrigens viele von diesen 80%-Schleifen in seinem Code hat und diesen vereinfachen möchte, dem kann ich nur Eclipse und sein "Clean Up" Wizard ans Herz legen, zu finden im Source-Menü. Dort gibt es die Option "Convert for loops to enhanced". Auf das obige Beispiel 2 angewendet erzeugt Eclipse bei mir folgenden Output:
for (String element : array)
{
System.out.println(element);
}
Nett. Und wer nun selbst noch Nachforschungen betreiben möchte, findet die Spezifikation unter http://jcp.org/aboutJava/communityprocess/final/jsr201/index.html .
Weitere Erkenntnisse, Meinungen und Erfahrungen aus Projekten sind hier jederzeit gerne als Kommentare gesehen.
Bis dahin grüßt Sie (zu 100%)
Roman Seibold
Nur mal kurz...

- Roman Seibold, Senior Consultant und technischer Leiter der Schulungsabteilung von aformatik
...einen JAX-WS WebService mit JBoss 5.1 erstellen.
von Roman Seibold
Vorgeschichte: der WebService
"Nur mal kurz" geht in der IT gar nicht. Musste ich neulich wieder feststellen. Eigentlich ging es hauptsächlich überhaupt nicht um einen WebService, aber ich brauchte einen, um etwas anderes tun zu können. Ist ja jetzt ganz einfach, mit Java EE 5 und JAX-WS. Dachte ich. Der WebService, als POJO nicht als EJB, ist mit JAX-WS tatsächlich schnell geschrieben. Hier, auch was den Rückgabetyp betrifft, sehr vereinfacht dargestellt:
import javax.jws.WebMethod;
import javax.jws.WebService;
@WebService
public class WebService
{
@WebMethod
public String getContent()
{
return "Juhu, tut!";
}
}
Das ist schon alles. Mit @WebService erkläre ich die Klasse zum Web-Service, mit @WebMethod wird klar, was als Operation für den Service im WSDL verfügbar wird.
Deployment
Jetzt beginnt der proprietäre Teil. Ein Java-EE-compliant Application- Server ist nur verpflichtet, einen Web-Access-Point für JAX-WS zur Verfügung zu stellen. Wie ist nicht gesagt. Das macht jeder Server anders, z.B. indem er einen automatisch aktivierten WebService- Router zur Verfügung stellt, wenn JAX-WS-Services erkannt werden. JBoss verfährt so nicht. Es gibt verschiedene Möglichkeiten, mit WebServices umzugehen. Mein POJO-WebService wird nur von außen erreichbar, wenn ich ihn als Servlet (!) definiere. Das sieht im Deployment-Deskriptor so aus:
<servlet> <servlet-name>JaxWSServlet</servlet-name> <servlet-class>
com.aformatik.biblio.service.BiblioAccessService
</servlet-class></servlet><servlet-mapping> <servlet-name>JaxWSServlet</servlet-name> <url-pattern>/BiblioAccess</url-pattern></servlet-mapping>
Das war schon die erste, nicht ganz intuitive Hürde. Weiterhin ist mein Projekt folgendermaßen - eigentlich recht simpel - strukturiert:
WebServiceEAR.ear
+-- WebServiceWeb.war
Das heißt, dass der WebService in einem Web-Modul definiert ist, das einem EAR-Modul zugeordnet ist. Das ganze schnell auf einen JBoss AS 5.1 deployed. Fehlerfrei. Gut.
Nur noch kurz...
Jetzt "nur noch kurz" den Client dazu... Geht eigentlich auch einfach. Man importiert das vom Server gemäß JAX-WS generierte WSDL-File und lässt sich die entsprechenden Client-Klassen erstellen und arbeitet damit. Die Frage ist nur: wo ist denn das WSDL-File? Angeblich gibt es bei JBoss eine URL unter der die deployten Services erreichbar sind. Bei mir sollte dies http://localhost:8080/jbossws/services sein. Tatsächlich, da ist auch was. Wenn man aber auf den Link zum Service klickt, dann kommt "Not Found". Hmmm...
Nach längerer Zeit wurde ich im "data"-Verzeichnis des Servers fündig, da liegt das generierte WSDL ebenfalls. Na gut, dann nimmt man halt das. Auf dieser Basis den Client generiert - und nichts tat. Not found. Ich habe also ein WSDL für einen Service, der angeblich verfügbar ist, aber nicht auf Zugriff reagiert. Das stimmte mich miß. Hier schloß sich nun eine längere Zeit für Recherche und Tests an. Offenbar war ich einem Problem begegnet, das noch niemand so hatte. Es ließ sich jedenfalls nicht auf die Schnelle im Internet etwas finden. Macht keiner WebServices, oder nicht mit JBoss, oder (noch) nicht mit JAX-WS?
Die Lösung
Auf einer einzigen, etwas versteckten JBoss-JIRA-Bug-Tracking Webseite, die eigentlich einen Bug im Zusammenhang mit JBoss ESB thematisierte, wurde ich fündig: die JBoss-native WebService-Implementierung hat einen Bug in der ausgelieferten Version 3.1.2. Der Bug kommt aber nur zur Geltung, wenn eine WAR-Datei, die einen Web-Service definiert, in einem EAR deployt wird. War bei mir ja der Fall. Lösung: die neuere Implementierung der WebServices 3.2.0 verwenden. Leider, leider ist die neueste Server-Version, die man von JBoss herunterladen kann, die 5.1 GA. Punkt. Kein Service-Release, kein sonstwas. Ist aber vom Mai, jetzt haben wir November. Haben die kein Release-Management? Na gut, dann basteln wir uns eben einen gepimpten Server. In einem Blog fand ich, wie man eine andere WebService-Implementierung ("Metro") aktualisiert. Ich wollte aber die JBoss-native. In diesem Beitrag ist die Download-Adresse der Metro-Implementierung angegeben, wenn man aber versucht, einfach mal ins Verzeichnis zu schauen, um nach den anderen Implementierungen zu suchen, dann klappt das aus Berechtigungsgründen nicht. Man muss den Dateinamen schon wissen! Jetzt hilft mal wieder nur "inspiriertes Raten" und es klappte mit dem URL http://jboss.org/file-access/default/members/jbossws/downloads/jbossws-native-3.2.0.GA.zip. War jetzt auch nicht so schwer. Danach die Steps ausgeführt, die der Kollege mit dem unaussprechlichen Namen "Prakashbabu" vorschlägt, und - voila - JBoss bootet noch (!) und zeigt im Log:
JBoss Web Services - Native Server
3.2.0.GA
Und, oh Wunder, jetzt tut sofort alles, wie beschrieben. Unter http://localhost:8080/jbossws/services/WebService?wsdl ist mein WSDL verfügbar, der Eclipse-Testclient und ein generierter Client-Code können sofort auf den Service zugreifen. Super! War ja auch ganz einfach. Naja, JAX-WS schon, nur der konkrete Teil mit Produkten und Versionen, der war mal wieder unschön.
Komisch aber schon, dass dieser Fehler kein größeres Echo hervorgerufen hat und dass die seit Mai verfügbare Download-Version von JBoss AS 5.1 immer noch diesen Bug beinhalten darf und dass es keine aktualisierte gibt. Das lässt schon den Verdacht etwas keimen, dass JAX-WS-WebServices noch nicht so verbreitet sind. Oder dass niemand die native Implementierung verwendet. Oder...







