Und die IDE wird besser und besser.
Details unter https://blog.jetbrains.com/idea/2018/01/intellij-idea-starts-2018-1-early-access-program/.
Und die IDE wird besser und besser.
Details unter https://blog.jetbrains.com/idea/2018/01/intellij-idea-starts-2018-1-early-access-program/.
Ab sofort können Spenden mit Bitcoins und Ether vorgenommen werden. Ich bin dankbar für jeden noch so kleinen Beitrag, der meine Arbeit versüßt 🙂
Nach Java 9 wird uns Java 10 beglücken. Die Zeitleiste sieht so aus:
2017/12/14 | Rampdown Phase One | |
2018/01/11 | All Tests Run | |
2018/01/18 | Rampdown Phase Two | |
2018/02/22 | Final Release Candidate | |
2018/03/20 | General Availability |
Java 10 ist laut http://openjdk.java.net/projects/jdk/10/ ab heute in der „Rampdown Phase One“ (http://openjdk.java.net/projects/jdk8/milestones#Rampdown_start):
Rampdown — Phases in which increasing levels of scrutiny are applied to incoming changes. In phase 1, only P1-P3 bugs can be fixed. In phase 2 only showstopper bugs can be fixed.
Neuerungen dir wir für Java 10 erwarten können, beschrieben im JEP:
286: Local-Variable Type Inference
296: Consolidate the JDK Forest into a Single Repository
304: Garbage-Collector Interface
307: Parallel Full GC for G1
310: Application Class-Data Sharing
312: Thread-Local Handshakes
313: Remove the Native-Header Generation Tool (javah)
314: Additional Unicode Language-Tag Extensions
316: Heap Allocation on Alternative Memory Devices
317: Experimental Java-Based JIT Compiler
319: Root Certificates
322: Time-Based Release Versioning
Es wird auch Java 10, Java 11, usw. heißen und nicht, wie vorher angedacht, Java 18, etc. nach Jahreszahlen. Aus dem http://openjdk.java.net/jeps/322:
$FEATURE
is incremented every six months: The March 2018 release is JDK 10, the September 2018 release is JDK 11, and so forth.
Microsoft Windows nutzt zur Datei- und Verzeichnisfreigabe, zur Freigabe von Druckern und Kommunikationsschnittstellen das Protokoll SMB (Server Message Block). Es ist weit verbreitet, und jede aktuelle Windows-Version lässt sich als Client und Server konfigurieren – gleichzeitig gibt es unter Unix das populäre Samba, einen SMB-Server unter Open Source, der von Andrew Tridgell und Kollegen entwickelt wurde.
Mithilfe der jCIFS-SMB-Bibliothek (http://jcifs.samba.org/) kann ein Java-Programm auf Datei- und Verzeichnisfreigaben zugreifen und Freigaben auflisten. jCIFS ist eine erweiterte Implementierung von CIFS und unterstützt Unicode, Batching, verschlüsselte Authentifizierung, Transactions, das Remote Access Protocol (RAP) und Weiteres. Die Bibliothek steht unter der LGPL.
Die Klassen jcifs.smb.SmbFile, SmbFileInputStream und SmbFileOutputStream verhalten sich ähnlich wie java.io.File, FileInputStream und FileOutputStream. Sie werden mit einem Dateipfad (URL) parametrisiert, der mit smb:// beginnt. Um eine Datei zu beziehen, muss vorher der Server spezifiziert werden. Dazu dienen Eigenschaften wie WINS. Sie werden mit Config.setProperty(„wins“, „IP-Adresse“); gesetzt.
Beispiel: Lies eine Datei aus, und kopiere sie um:
InputStream in = new SmbFileInputStream( "smb://user:passwd@host/c/My Documents/doc.txt" ); Path target = … Files.copy( in, target );
Unsere nächste Klasse bringt uns etwas näher an das HTML-Format heran. Wir wollen eine Klasse HTMLWriter entwerfen, die FilterWriter erweitert und Textausgaben in HTML konvertiert. In HTML werden Tags eingeführt, die vom Browser erkannt und besonders behandelt werden. Findet etwa der Browser im HTML-Text eine Zeile der Form <strong>Dick</strong>, so stellt er den Inhalt „Dick“ in fetter Schrift dar, da das <strong>-Element den Zeichensatz umstellt. Alle Tags werden in spitzen Klammern geschrieben. Daraus ergibt sich, dass HTML einige spezielle Zeichenfolgen (Entities genannt) verwendet. Wenn diese Zeichen auf der HTML-Seite dargestellt werden, muss dies durch spezielle Zeichensequenzen geschehen:
Kommen diese Zeichen im Quelltext vor, so muss unser HTMLWriter diese Zeichen durch die entsprechende Sequenz ersetzen. Andere Zeichen sollen nicht ersetzt werden.
Den Browsern ist die Struktur der Zeilen in einer HTML-Datei egal. Sie formatieren wiederum nach speziellen Tags. Zeilenvorschübe etwa werden mit <br/> eingeleitet. Unser HTMLWriter soll zwei leere Zeilen durch das Zeilenvorschub-Element <br/> markieren.
Alle sauberen HTML-Dateien haben einen wohldefinierten Anfang und ein wohldefiniertes Ende. Das folgende kleine HTML-Dokument ist wohlgeformt und zeigt, was unser Programm später erzeugen soll:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head><title>Superkreativer Titel</title></head> <body><p> Und eine Menge von Sonderzeichen: < und > und & Zweite Zeile <br/> Leerzeile Keine Leerzeile danach </p></body></html>
Der Titel der Seite sollte im Konstruktor übergeben werden können. Hier ist nun das Programm für den HTMLWriter:
package com.tutego.insel.io.stream; import java.io.*; class HTMLWriter extends FilterWriter { private boolean newLine; /** * Creates a new filtered HTML writer with a title for the web page. * * @param out a Writer object to provide the underlying stream. * @throws IOException if the header cannot be written */ public HTMLWriter( Writer out, String title ) throws IOException { super( out ); out.write( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"" + " \"http://www.w3.org/TR/html4/strict.dtd\">\n" ); out.write( "<html><head><title>" + title + "</title></head>\n<body><p>\n" ); } /** * Creates a new filtered HTML writer with no title for the web page. * * @param out a Writer object to provide the underlying stream. */ public HTMLWriter( Writer out ) { super( out ); } /** * Writes a single character. */ @Override public void write( int c ) throws IOException { switch ( c ) { case '<': out.write( "<" ); newLine = false; break; case '>': out.write( ">" ); newLine = false; break; case '&': out.write( "&" ); newLine = false; break; case '\n': if ( newLine ) { out.write( "<br/>\n" ); newLine = false; } else out.write( "\n" ); newLine = true; break; case '\r': break; // ignore default : out.write( c ); newLine = false; } } /** * Writes a portion of an array of characters. * * @param cbuf Buffer of characters to be written * @param off Offset from which to start reading characters * @param len Number of characters to be written * @exception IOException If an I/O error occurs */ @Override public void write( char[] cbuf, int off, int len ) throws IOException { for ( int i = off; i < len; i++ ) write( cbuf[ i ] ); } /** * Writes a portion of a string. * * @param str String to be written. * @param off Offset from which to start reading characters * @param len Number of characters to be written * @exception IOException If an I/O error occurs */ @Override public void write( String str, int off, int len ) throws IOException { for ( int i = off; i < len; i++ ) write( str.charAt( i ) ); } /** * Closes the stream. * * @throws IOException If the prolog can not be written or the underlying stream * not be closed */ @Override public void close() throws IOException { try { out.write( "</p></body></html>" ); } finally { out.close(); // Ignoriere, falls out.close() und out.write() knallt } } }
Ein Demo-Programm soll die aufbereiteten Daten in einen StringWriter schreiben:
StringWriter sw = new StringWriter(); try ( HTMLWriter html = new HTMLWriter( sw, "Superkreativer Titel" ); PrintWriter pw = new PrintWriter( html ) ) { pw.println( "Und eine Menge von Sonderzeichen: < und > und &" ); pw.println( "Zweite Zeile" ); pw.println(); pw.println( "Leerzeile" ); pw.println( "Keine Leerzeile danach" ); } System.out.println( sw );
Unser nächstes Beispiel ist eine Klasse, die den FilterReader so erweitert, dass HTML-Tags überlesen werden. Die Klasse FilterReader deklariert den notwendigen Konstruktor zur Annahme des Reader, der die wirklichen Daten liefert, und überschreibt zwei read(…)-Methoden. Die read()-Methode ohne Parameter – die ein int für ein gelesenes Zeichen zurückgibt – legt einfach ein 1 Zeichen großes Feld an und ruft dann die zweite überschriebene read(char[], int, int)-Methode auf, die die Daten in ein Feld liest. Da dieser Methode neben dem Feld auch noch die Größe übergeben werden kann, müssen wirklich so viele Zeichen gelesen werden. Es reicht einfach nicht aus, die übergebene Anzahl von Zeichen vom tiefer liegenden Reader zu lesen, sondern hier müssen wir beachten, dass eingestreute Tags nicht zählen. Die Zeichenkette <p>Hallo<p> ist ja nur fünf Zeichen lang und nicht elf!
package com.tutego.insel.io.stream; import java.io.*; public class HTMLReader extends FilterReader { private boolean inTag = false; public HTMLReader( Reader in ) { super( in ); } @Override public int read() throws IOException { char[] buf = new char[ 1 ]; return read( buf, 0, 1 ) == –1 ? –1 : buf[ 0 ]; } @Override public int read( char[] cbuf, int off, int len ) throws IOException { int numchars = 0; while ( numchars == 0 ) { numchars = in.read( cbuf, off, len ); if ( numchars == –1 ) // EOF? return –1; int last = off; for ( int i = off; i < off + numchars; i++ ) { if ( ! inTag ) { if ( cbuf[ i ] == '<' ) inTag = true; else cbuf[ last++ ] = cbuf[ i ]; } else if ( cbuf[ i ] == '>' ) inTag = false; } numchars = last – off; } return numchars; } }
Ein Beispielprogramm soll die Daten aus einem StringReader ziehen. Der HTMLReader bekommt diesen StringReader und wird selbst von Scanner genutzt, damit wir die komfortable nextLine()-Methode nutzen können. Da hier keine externen Ressourcen vorkommen, müssen wir nichts schließen, und ein try mit Ressourcen kann entfallen.
String s = "<html>Hallo! <b>Ganz schön fett.</b> " + "Ah, wieder normal.</html>"; Reader sr = new StringReader( s ); Reader hr = new HTMLReader( sr ); Scanner scanner = new Scanner( hr ); while ( scanner.hasNextLine() ) System.out.println( scanner.nextLine() );
Es produziert dann die einfache Ausgabe:
Hallo! Ganz schön fett. Ah, wieder normal.
Wir wollen im Folgenden einen Filter schreiben, der alle in den Strom geschriebenen Zeichen in Kleinbuchstaben umwandelt. Drei Dinge sind für einen eigenen FilterWriter nötig:
class LowerCaseWriter extends FilterWriter { public LowerCaseWriter( Writer writer ) { super( writer ); } @Override public void write( int c ) throws IOException { out.write( Character.toLowerCase((char)c) ); } @Override public void write( char[] cbuf, int off, int len ) throws IOException { write( String.valueOf( cbuf ), off, len ); } @Override public void write( String s, int off, int len ) throws IOException { out.write( s.toLowerCase(), off, len ); } }
Und die Nutzung sieht dann so aus:
StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter( new LowerCaseWriter( sw ) ); pw.println( "Eine Zeile für klein und groß" ); System.out.println( sw.toString() );
Details bei Oracle im Blog: https://blogs.oracle.com/theaquarium/opening-up-ee-update.
JAX-RS erlaubt grundsätzlich alle MIME-Typen, und die Daten selbst können auf verschiedene Java-Datentypen übertragen werden. So ist es egal, ob bei Textdokumenten zum Beispiel der Rückgabetyp String oder OutputStream ist; selbst ein File-Objekt lässt sich zurückgeben. Für einen Parametertyp – Parameter werden gleich vorgestellt – gilt das Gleiche: JAX-RS ist hier recht flexibel und kann über einen InputStream oder Writer einen String entgegennehmen. (Reicht das nicht, können so genannte Provider angemeldet werden.)
Bei XML-Dokumenten kommt hinzu, dass JAX-RS wunderbar mit JAXB zusammenspielt.
Dazu ein Beispiel für einen Dienst hinter der URL http://localhost:8080/api/dating/serverinfo, der eine Serverinformation im XML-Format liefert. Das XML wird automatisch von JAXB generiert.
@GET @Path( "serverinfo" ) @Produces( MediaType.TEXT_XML ) public ServerInfo serverinfo() { ServerInfo info = new ServerInfo(); info.server = System.getProperty( "os.name" )+" "+System.getProperty( "os.version" ); return info; } @XmlRootElement class ServerInfo { public String server; }
Die Klasse ServerInfo ist eine JAXB-annotierte Klasse. In der eigenen JAX-RS-Methode serverinfo() wird dieses ServerInfo-Objekt aufgebaut, das Attribut gesetzt und dann zurückgegeben; der Rückgabetyp ist also nicht String wie vorher, sondern explizit ServerInfo. Dass der MIME-Typ XML ist, sagt @Produces(MediaType.TEXT_XML). Und noch eine Annotation nutzt das Beispiel: @Path. Lokal an der Methode bedeutet es, dass der angegebene Pfad zusätzlich zur Pfadangabe an der Klasse gilt. Also ergibt sich der komplette Pfad aus: Basispfad + „dating“ + „/“ + „serverinfo“. Wir können http://localhost:8080/api/dating/serverinfo im Browser eingeben.
Ist der Client eines REST-Aufrufs ein JavaScript-Programm in einem Webbrowser, ist es in der Regel praktischer, statt XML das Datenformat JSON zu verwenden. JAX-RS bindet drei Möglichkeiten zum Senden von JSON:
Schauen wir uns die zweite Lösung an. Jersey unterstützt von Haus aus Jackson, MOXy und Jettison als JSON-Objekt-Mapper. Um MOXy einzusetzen müssen weitere Java-Archive in den Klassenpfad aufgenommen werden. Wir können die Abhängigkeiten über Maven beschreiben, dann ist folgendes in der pom.xml aufzunehmen:
<dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-moxy</artifactId> <version>2.25</version> </dependency>
Oder zu Fuß müssen die folgenden JAR-Dateien geladen und dann im Klassenpfad aufgenommen werden:
@GET @Path( "jsonserverinfo" ) @Produces( MediaType.APPLICATION_JSON ) public ServerInfo jsonserverinfo() { return serverinfo(); }
Das reicht schon aus, und der Server sendet ein JSON-serialisiertes Objekt.
Die JAX-RS-API bietet mit dem MIME-Typ noch eine Besonderheit, dass der Server unterschiedliche Formate liefern kann, je nachdem, was der Client verarbeiten möchte oder kann. Der Server macht das mit @Produces klar, denn dort kann eine Liste von MIME-Typen stehen. Soll der Server XML und JSON generieren können, schreiben wir:
@GET @Path( "jsonxmlserverinfo" ) @Produces( { MediaType.TEXT_XML, MediaType.APPLICATION_JSON } ) public ServerInfo jsonxmlserverinfo() { return serverinfo(); }
Kommt der Client mit dem Wunsch nach XML, bekommt er XML, möchte er JSON, bekommt er JSON. Die Jersey-Client-API teilt über request(String) bzw. request(MediaType… types) mit, was ihr Wunschtyp ist. (Dieser Typwunsch ist eine Eigenschaft von HTTP und nennt sich Content Negotiation.)
WebTarget wr1 = ClientBuilder.newClient().target( "http://localhost:8080/api" ); Builder b1 = wr1.path( "dating" ).path( "jsonxmlserverinfo" ) .request( MediaType.APPLICATION_JSON ); System.out.println( b1.get( ServerInfo.class ).getServer() ); // Windows 10 10.0 WebTarget wr2 = ClientBuilder.newClient().target( "http://localhost:8080/api" ); Builder b2 = wr2.path( "dating" ).path( "jsonxmlserverinfo" ) .request( MediaType.TEXT_XML ); System.out.println( b2.get( ServerInfo.class ).getServer() ); // Windows 10 10.0 WebTarget wr3 = ClientBuilder.newClient().target( "http://localhost:8080/api" ); Builder b3 = wr3.path( "dating" ).path( "jsonxmlserverinfo" ) .request( MediaType.TEXT_PLAIN ); try { System.out.println( b3.get( ServerInfo.class ).getServer() ); } catch ( Exception e ) { System.out.println( e ); // javax.ws.rs.NotAcceptableException: HTTP 406 Not Acceptable }
Passt die Anfrage auf den Typ von @Produces, ist alles prima, ohne Übereinstimmung gibt es einen Fehler. Bei der letzten Zeile gibt es eine Ausnahme („javax.ws.rs.NotAcceptableException: HTTP 406 Not Acceptable“), da JSON und XML eben nicht purer Text sind.
Im Abschnitt „Wie sieht ein REST-URI aus?“ in Abschnitt 15.2.1 wurde ein Beispiel vorgestellt, wie Pfadangaben aussehen, wenn sie einen RESTful Service bilden:
http://www.tutego.de/blog/javainsel/category/java-7/page/2/
Als Schlüssel-Wert-Paare lassen sich festhalten: category=java-7 und page=2. Der Server wird die URL auseinanderpflücken und genau die Blog-Einträge liefern, die zur Kategorie „java-7“ gehören und sich auf der zweiten Seite befinden.
Bisher sah unser REST-Service auf dem Endpunkt /api/dating/ so aus, dass einfach ein String zurückgegeben wird. Üblicherweise gibt es aber unterschiedliche URLs, die Operationen wie „finde alle“ oder „finde alle mit der Einschränkung X“ abbilden. Bei unseren Dating-Dienst wollen wir dem Client drei Varianten zur Abfrage anbieten (mit Beispiel):
Das erste Beispiel macht deutlich, dass hier ohne explizite Angabe weiterer Einschränkungskriterien alle Nachrichten erfragt werden sollen, während mit zunehmend längerer URL weitere Einschränkungen dazukommen.
Die JAX-RS-API erlaubt es, dass Parameter (wie eine ID oder ein Such-String) leicht eingefangen werden können. Für die drei möglichen URLs entstehen zum Beispiel drei überladene Methoden:
@GET @Produces( MediaType.TEXT_PLAIN ) public String meet() ... @GET @Path( "gender/{gender}" ) @Produces( MediaType.TEXT_PLAIN ) public String meet( @PathParam( "gender" ) String gender ) { return String.format( "Geschlecht = %s", gender ); } @GET @Produces( MediaType.TEXT_PLAIN ) @Path( "gender/{gender}/age/{age}" ) public String meet( @PathParam( "gender" ) String gender, @PathParam( "age" ) String age ) { return String.format( "Geschlecht = %s, Altersbereich = %s", gender, age ); }
Die bekannte @Path-Annotation enthält nicht einfach nur einen statischen Pfad, sondern beliebig viele Platzhalter in geschweiften Klammern. Der Name des Platzhalters taucht in der Methode wieder auf, nämlich dann, wenn er mit @PathParam an einen Parameter gebunden wird. Jersey parst für uns die URL und füllt die Parametervariablen passend auf bzw. ruft die richtige Methode auf. Da die JAX-RS-Implementierung den Wert füllt, nennt sich das auch JAX-RS-Injizierung.
URL-Endung | Aufgerufene Methode |
/api/dating/ | meet() |
/api/dating/gender/nasi | meet( String gender ) |
/api/dating/gender/nasi/age/18-28 | meet( String gender, String age ) |
Welche URL zu welcher Methode führt
Die Implementierungen der Methoden würden jetzt an einen Daten-Service gehen und die selektierten Datensätze zurückgeben. Das zeigt das Beispiel nicht, da dies eine andere Baustelle ist.
Tipp: Wenn Parameter falsch sind, kann eine Methode eine besondere Ausnahme vom Typ javax.ws.rs.WebApplicationException (dies ist eine RuntimeException) erzeugen. Im Konstruktor von javax.ws.rs.WebApplicationException lässt sich ein Statuscode als int oder als Aufzählung vom Typ Response.Status übergeben, etwa new WebApplicationException(Response.Status. EXPECTATION_FAILED).
Wenn die URLs in dem Format schlüssel1/wert1/schlüssel2/wert2 aufgebaut sind, dann ist ein Aufbau einfach durch Kaskadierung der path(…)-Methoden umzusetzen:
System.out.println( ClientBuilder.newClient().target( "http://localhost:8080/api" ) .path( "dating" ).path( "gender" ).path( "nasi" ) .request().get( String.class ) ); // Geschlecht = nasi System.out.println( ClientBuilder.newClient().target( "http://localhost:8080/api/dating" ) .path( "gender" ).path( "nasi" ).path( "age" ).path( "18-28" ) .request().get( String.class ) ); // Geschlecht = nasi, Altersbereich = 18-28
Schlüssel-Wert-Paare lassen sich auch auf anderen Wegen übermitteln statt nur auf dem Weg über schlüssel1/wert1/schlüssel2/wert2. Besonders im Web und für Formularparameter ist die Kodierung über schlüssel1=wert1&schlüssel2=wert2 üblich. Auch das kann in JAX-RS und mit der Jersey-Client-API abgebildet werden:
Hinweis
Es gibt eine Reihe von Dingen, die in Methoden per Annotation übermittelt werden können, und nicht nur @PathParam und @QueryParam. Dazu kommen noch Dinge wie @HeaderParam für den HTTP-Request-Header, @CookieParam für Cookies, @Context für Informationsobjekte und weitere Objekte.
Zum Senden von Daten an einen REST-Service ist die HTTP-PUT-Methode gedacht. Die Implementierung einer Java-Methode kann so aussehen:
@PUT @Path( "message/{user}" ) @Consumes( MediaType.TEXT_PLAIN ) public Response postMessage( @PathParam( "user" ) String user, String message ) { System.out.printf( "%s sendet '%s'%n", user, message ); return Response.noContent().build(); }
Zunächst gilt, dass statt @GET ein @PUT die Methode annotiert. @Consumes hält den MIME-Typ dieser gesendeten Daten fest. Ein zusätzlicher @PathParam fängt die Benutzerkennung ein, die dann mit der gesendeten PUT-Nachricht auf der Konsole ausgegeben wird.
Diese beiden Annotationen @PUT und @Consumes sind also nötig. Eine Rückgabe in dem Sinne hat die Methode nicht, und es ist umstritten, ob ein REST-PUT überhaupt neben dem Statuscode etwas zurückgeben soll. Daher ist die Rückgabe ein spezielles JAX-RS-Objekt vom Typ Response, das hier für 204, „No Content“, steht. Ein 204 kommt auch immer dann automatisch zurück, wenn eine Methode void als Rückgabe deklariert.
Ein Invocation.Builder bietet neben get(…) auch die anderen Java-Methoden für HTTP-Methoden, also delete(…), post(…), options(…), … und eben auch put(…) zum Schreiben.
Client client = ClientBuilder.newClient(); WebTarget target = client.target( "http://localhost:8080/api" ); Response response = target.path( "dating" ).path( "message" ) .path( "Ha" ).request().put( Entity.text("Hey Ha!") ); System.out.println( response );
Die put(…)-Methode erwartet als Argument den Typ Entity, und zum Objektaufbau gibt es diverse statische Methoden in Entity. Es ist Entity.text(„Hey Chris“) eine Abkürzung für Entity.entity(„Hey Chris“, MediaType.TEXT_PLAIN).
Die REST-Schnittstelle ist ein externer Endpunkt einer Software und unterliegt den gleichen Modifikationen wie übliche Software: Schnittstellen ändern sich, die ausgetauschten Objekte bekommen zusätzliche Felder, usw. Ändert sich die Schnittstelle, erzeugt das eine neue Version. Alte APIs sollte ein Server eine Zeit lang beibehalten, damit nicht von einem Tag auf dem anderen eine vielleicht große Anzahl von Klienten mit der älteren Schnittstelle kommunikationslos sind.
Prinzipiell gibt es drei Ansätze bei der Versionierung von REST-APIs; die ersten beiden Varianten nutzen die Möglichkeiten der URL bzw. des HTTP:
Alle Lösungen lassen sich prinzipiell mit JAX-RS umsetzen, wobei die Lösung 1 am Einfachsten ist.
Dieser Abschnitt stellt das Architekturprinzip REST vor und anschließend den Java-Standard JAX-RS. Es folgen Beispiele mit der JAX-RS-Referenzimplementierung Jersey.
Bei RESTful Services liegt das Konzept zugrunde, dass eine Ressource über einen Webserver verfügbar ist und eindeutig über einen URI identifiziert wird.
Da unterschieden werden muss, ob eine Ressource neu angelegt, gelesen, aktualisiert, gelöscht oder aufgelistet werden soll, werden dafür die bekannten HTTP-Methoden verwendet:
HTTP-Methode | Operation |
GET | Listet Ressourcen auf oder holt eine konkrete Ressource. |
PUT | Aktualisiert eine Ressource. |
DELETE | Löscht eine Ressource oder eine Sammlung von Ressourcen. |
POST | Semantik kann variieren, in der Regel aber geht es um das Anlegen einer neuen Ressource. |
HTTP-Methoden und übliche assoziierte Operationen
Anders als SOAP ist REST kein entfernter Methodenaufruf, sondern eher vergleichbar mit den Kommandos INSERT, UPDATE, DELETE und SELECT in SQL.
Die Idee, Web-Services mit dem Webstandard HTTP aufzubauen, beschrieb Roy Thomas Fielding im Jahr 2000 in seiner Dissertation „Architectural Styles and the Design of Network-based Software Architectures“. Das Architekturprinzip nennt er Representational State Transfer (REST) – der Begriff ist neu, aber das Konzept ist alt. Aber so wie im richtigen Leben setzen sich manche Dinge erst spät durch. Das liegt auch daran, dass SOAP-basierte Web-Services immer komplizierter wurden und sich die Welt nach etwas anderem sehnte. (Daher beginnen wir im Buch auch mit REST, und dann wird SOAP folgen.)
Auf der einen Seite stehen die HTTP-Methoden GET, PUT, POST, DELETE und auf der anderen Seite die URIs, die die Ressource kennzeichnen. Ein gutes Beispiel einer REST-URL ist der Blog vom Java-Buch:
http://www.tutego.de/blog/javainsel/category/java-9/page/2/
Das Ergebnis ist ein HTML-Dokument mit ausschließlich den Beiträgen aus der Kategorie Java 9 und nur denen auf der zweiten Seite.
Da auf den ersten Blick jede HTTP-Anfrage an einen Webserver wie ein REST-Aufruf aussieht, sollten wir uns im Klaren darüber sein, was denn kein REST-Aufruf ist. Eine URL wie http://www.bing.com/search?q=tutego ist erst einmal nicht der typische REST-Stil, da es keine Ressource gibt, die angesprochen wird. Da REST als leichtgewichtig und cool gilt, geben sich natürlich gerne Dienste ein bisschen RESTig. Ein Beispiel ist Flickr, ein Fotoservice von Yahoo. Das Unternehmen wirbt mit einer REST-API, aber es ist alles andere als REST und kein gutes Beispiel.[1] Das Gleiche gilt auch für Twitter, das lediglich einen rudimentären REST-Ansatz hat, aber in der Öffentlichkeit als REST pur wahrgenommen wird.
Für Java gibt es mit JAX-RS einen Standard zum Deklarieren von REST-basierten Web-Services. Er wurde in der JSR-311, „JAX-RS: The Java API for RESTful Web Services“, definiert. Es fehlt noch eine Implementierung, damit wir Beispiele ausprobieren können:
Mit Jersey lässt sich entweder ein Servlet-Endpunkt definieren, sodass der RESTful Web-Service in einem Servlet-Container wie Tomcat läuft, oder der eingebaute Mini-HTTP-Server genutzt wird. Wir werden im Folgenden mit dem eingebauten Server arbeiten.
Wenn wir den händischen Weg nehmen ist es etwas aufwändiger, die einzelnen Jar-Dateien zusammenzusammeln. Die Jersey-Homepage https://jersey.github.io/ verweist für den Download auf https://jersey.github.io/download.html. Klicken wir dort auf
Jersey JAX-RS 2.0 RI bundle […] contains the JAX-RS 2.0 API jar, all the core Jersey module jars as well as all the required 3rd-party dependencies.
dann startet der Download von jaxrs-ri-2.x.y.zip. Das ZIP-Archiv können wir auspacken und dann folgende Archive in den Klassenpfad aufnehmen:
JAX-RS definiert einige Annotationen, die zentrale Konfigurationen bei RESTful Web-Services vornehmen, etwa Pfadangaben, Ausgabeformate oder Parameter. In den nächsten Abschnitten werden diese Annotationen an unterschiedlichen Beispielen vorgestellt.
Beginnen wir mit einem REST-Service, der einen simplen Text-String liefert:
package com.tutego.insel.rest; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; @Path( "dating" ) public class DatingResource { @GET @Produces( MediaType.TEXT_PLAIN ) public String meet() { return "Afra, Ange, Ceri, Dara, Ha, Jun, Sevan"; } }
Die ersten beiden Annotationen sind zentral:
Ein Java EE-Application-Server würde die Klasse aufgrund der Annotationen gleich einlesen und als REST-Service anmelden. Da wir die Klasse jedoch in der Java SE ohne Application-Server verwenden, muss ein REST-Server von Hand aufgebaut werden. Das ist aber problemlos, denn dafür haben wir jersey-container-jdk-http-2.x.y.jar eingebunden, sodass sich Jersey in den im JDK eingebauten HTTP-Server integriert.
ResourceConfig rc = new ResourceConfig().packages( "com.tutego.insel.rest" ); HttpServer server = JdkHttpServerFactory.createHttpServer( URI.create( "http://localhost:8080/api" ), rc ); JOptionPane.showMessageDialog( null, "Ende" ); server.stop( 0 );
Nach dem Start des Servers scannt Jersey die Klassen im genannten Paket daraufhin ab, ob sie passende JAX-RS-Annotationen tragen. Das Class-Scanning wurde aktiviert mit der package(…)-Methode beim ResourceConfig, es ist aber auch möglich, im Konstruktor von ResourceConfig direkt die JAW-RS-Ressourcen über ihre Class-Objekte aufzuzählen.
Die statische Methode JdkHttpServerFactory.createHttpServer(…) liefert ein Objekt vom Typ com.sun.net.httpserver.HttpServer, das Teil der internen Java-API ist. Die JdkHttpServerFactory stammt aus dem internen Jersey-Paket.
Bei createHttpServer(…) ist als Testpfad „http://localhost:8080/api“ eingetragen, und zusammen mit @Path(„dating“) an der Klasse ergibt sich der Pfad http://localhost:8080/api/dating für die Ressource. Wir können die URL im Browser eintragen.
Sich im Browser das Ergebnis eines REST-Aufrufs anzuschauen, ist nicht das übliche Szenario. Oft sind es JavaScript-Programme im Browser, die REST-Aufrufe starten und die konsumierten Daten auf einer Webseite integrieren.
Ist es ein Java-Programm, das einen REST-Dienst anzapft, so kann dies mit einer einfachen URLConnection erledigt werden, was eine Standardklasse aus dem java.net-Paket ist, um auf HTTP-Ressourcen zuzugreifen. Doch Jersey definiert eine standardisierte API zum einfachen Zugriff. Es reicht ein Einzeiler:
System.out.println( ClientBuilder.newClient().target( "http://localhost:8080/api" ) .path( "dating" ).request().get( String.class ) );
Der API-Stil, den die Bibliotheksautoren hier verwenden, nennt sich Fluent-API; Methodenaufrufe werden verkettet, um alle Parameter wie URL oder MIME-Typ für die Anfrage zu setzen. Das ist eine Alternative zu den bekannten Settern auf JavaBeans bzw. einen überladenen Konstruktor mit unübersichtlichen Parameterlisten.
Um die Typen bei der javax.ws.rs.client-API etwas besser zu verstehen, schreiben wir alles ganz vollständig aus:
Client client = ClientBuilder.newClient(); WebTarget target = client.target( "http://localhost:8080/api" ); WebTarget resourceTarget = target.path( "dating" ); Invocation.Builder request = resourceTarget.request( MediaType.TEXT_PLAIN ); Response response = request.get(); System.out.println( response.readEntity( String.class ) );
Das Response-Objekt hat auch eine Methode getStatus() für den HTTP-Statuscode und hasEntry(), um zu prüfen, ob es ein Ergebnis gibt und keinen Fehler. Apropos Fehler: Ist die URL falsch, gibt es eine RuntimeException – geprüfte Ausnahmen verwendet Jersey nicht.
Exception in thread „main“ javax.ws.rs.NotFoundException: HTTP 404 Not Found
[1] Roy Fielding meint dazu nur: „Flickr obviously don’t have a clue what REST means since they just use it as an alias for HTTP. Perhaps that is because the Wikipedia entry is also confused. I don’t know.“ (Quelle: http://roy.gbiv.com/untangled/2008/no-rest-in-cmis).
Alle Neuigkeiten unter http://docs.spring.io/sts/nan/v390/NewAndNoteworthy.html
https://projects.eclipse.org/releases/oxygen
Alle Neuerungen unter https://www.eclipse.org/oxygen/noteworthy/.
Unter Java 9 bedarf es Vorbereitungen für den Start:
If you are launching eclipse with a Java 9 build (either via system or via -vm option in eclipse.ini file) then, in eclipse.ini
file, after -vmargs
line add --add-modules=ALL-SYSTEM
. This should launch eclipse. If you get an IllegalAccessException
or InaccessibleObjectException
, you can add --permit-illegal-access
additionally after the -vmargs
.
e.g.
... --launcher.appendVmargs -vmargs --add-modules=ALL-SYSTEM --permit-illegal-access ...
The switches can be entered on the command line after -vmargs
, e.g.
$ ./eclipse -vm *[path to jdk9]*/bin -vmargs --add-modules=ALL-SYSTEM --permit-illegal-access
Weiterhin:
The Java™ 9 specification has not been released yet and so support has not yet been integrated into our standard download packages. You can add an early access preview to the Eclipse IDE, Oxygen Edition (4.7).
The Eclipse Java™ 9 Support (BETA) contains the following:
Install the beta by dragging the install button onto your running Eclipse IDE, Oxygen Edition instance.
Zum Verarbeiten von JSON-Dokumenten gibt es in der Java SE keine Standardklassen, sodass sich eine Reihe von Open-Source-Bibliotheken herausgeprägt haben; Jackson (http://tutego.de/go/jackson) gehört zu den populärsten Lösungen. 2013 wurde dann die JSR 353, „Java API for JSON Processing“ verabschiedet, die Teil der Java EE 7 ist.
Wir können die JSON-API in unseren Java SE-Programmen nutzen, müssen dafür aber Java-Archive im Klassenpfad einbinden. Die Referenzimplementierung befindet sich unter https://javaee.github.io/jsonp/. Am Einfachsten haben es Maven-Nutzer, sie binden in ihre POM folgende Abhängigkeiten ein:
<dependency> <groupId>javax.json</groupId> <artifactId>javax.json-api</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.json</artifactId> <version>1.1</version> </dependency>
Der Typ JsonObject ist in der API zentral, denn er definiert ein hierarchisches Model mit den Schlüssel-Wertepaaren eines JSON-Objekts. Um ein JsonObject aufzubauen können wir über den JsonObjectBuilder gehen, oder wir lassen den Parser JsonReader aus einer String-Repräsentation ein JsonObject erzeugen. Zum formatierten Schreiben in einen Ausgabestrom können wir einen einfachen JsonWriter von der Json-Klasse holen – es geht aber noch einfacher über toString() – oder über die JsonWriterFactory arbeiten, falls wir eine hübsche eingerückte Ausgabe wünschen. Ein Beispiel:
Point p = new Point( 10, 20 ); JsonObjectBuilder objBuilder = Json.createObjectBuilder() .add( "x", p.x ) .add( "y", p.y ); JsonObject jsonObj = objBuilder.build(); System.out.println( jsonObj ); // {"x":10,"y":20} Json.createWriter( System.out ).write( jsonObj ); // {"x":10,"y":20} Map<String, Boolean> config = new HashMap<>(); config.put( JsonGenerator.PRETTY_PRINTING, true ); JsonWriterFactory writerFactory = Json.createWriterFactory( config ); StringWriter out = new StringWriter(); writerFactory.createWriter( out ).write( jsonObj ); System.out.println( out ); // {\n "x": 10, ... JsonReader reader = Json.createReader( new StringReader( out.toString() ) ); System.out.println( reader.readObject().getInt( "x" ) ); // 10
Soll der assoziierte Wert ein Array sein, so wird dieser mit Json.createArrayBuilder().add(..).add(..) aufgebaut und gefüllt.
So wie es für XML eine Pull-API gibt, existiert diese auch für JSON-Dokumente; das ist von Vorteil, wenn die Daten sehr groß sind. Ein Beispiel zeigt das sehr gut:
URL url = new URL( "https://data.cityofnewyork.us/api/views/25th-nujf/rows.json?accessType=DOWNLOAD" ); try ( JsonParser parser = Json.createParser( url.openStream() ) ) { while ( parser.hasNext() ) { switch ( parser.next() ) { case KEY_NAME: case VALUE_STRING: System.out.println( parser.getString() ); break; case VALUE_NUMBER: System.out.println( parser.getBigDecimal() ); break; case VALUE_TRUE: System.out.println( true ); break; case VALUE_FALSE: System.out.println( false ); break; case VALUE_NULL: case START_ARRAY: case END_ARRAY: case START_OBJECT: case END_OBJECT: // Ignore } } }
Da es für existierende XML-Dateien mühselig ist, die annotierten JavaBeans von Hand aufzubauen, gibt es einen Generator, genannt Java Architecture for XML Binding Compiler, kurz xjc. Er kann von der Kommandozeile, von Maven, aus einem Ant-Skript oder auch von Entwicklungsumgebungen aus aufgerufen werden. Er nimmt eine XML-Schema-Datei und generiert die Java-Klassen und eine ObjectFactory, die als – wie der Name schon sagt – Fabrik für die gemappten Objekte aus den XML-Elementen fungiert.
Im ersten Schritt wechseln wir auf die Kommandozeile und testen entweder, ob das bin-Verzeichnis vom JDK im Suchpfad ist, oder wir wechseln in das bin-Verzeichnis, sodass wir xjc direkt aufrufen können, und folgende Ausgabe erscheint:
$ xjc -help Verwendung: xjc [-options ...] <schema file/URL/dir/jar> ... [-b <bindinfo>] ... Wenn dir angegeben wird, werden alle Schemadateien im Verzeichnis kompiliert. Wenn jar angegeben wird, wird die /META-INF/sun-jaxb.episode-Binding-Datei kompiliert. Optionen: -nv : Führt keine strikte Validierung der Eingabeschemas durch -extension : Lässt Herstellererweiterungen zu - Befolgt die Kompatibilitätsregeln und App E.2 der JAXB-Spezifikation nicht strikt -b <file/dir> : Gibt externe Bindings-Dateien an (jede <file> muss ihre eigene Option -b haben) Wenn ein Verzeichnis angegeben wird, wird **/*.xjb durchsucht -d <dir> : Generierte Dateien werden in diesem Verzeichnis gespeichert -p <pkg> : Gibt das Zielpackage an -httpproxy <proxy> : set HTTP/HTTPS proxy. Format ist [user[:password]@]proxyHost:proxyPort -httpproxyfile <f> : Wird wie -httpproxy verwendet, verwendet jedoch das Argument in einer Datei zum Schutz des Kennwortes -classpath <arg> : Gibt an, wo die Benutzerklassendateien gefunden werden -catalog <file> : Gibt Katalogdateien zur Auflösung von externen Entity-Referenzen an Unterstützt TR9401, XCatalog und OASIS-XML-Katalogformat. -readOnly : Generierte Dateien werden im schreibgeschützten Modus gelesen -npa : Unterdrückt die Generierung von Annotationen auf Packageebene (**/package-info.java) -no-header : Unterdrückt die Generierung eines Datei-Headers mit Zeitstempel -target (2.0|2.1) : Verhält sich wie XJC 2.0 oder 2.1 und generiert Code, der keine 2.2-Features verwendet. -encoding <encoding> : Gibt Zeichencodierung für generierte Quelldateien an -enableIntrospection : Aktiviert die ordnungsgemäße Generierung von booleschen Gettern/Settern zur Aktivierung von Bean Introspection-APIs -contentForWildcard : Generiert Contenteigenschaft für Typen mit mehreren von xs:any abgeleiteten Elementen -xmlschema : Behandelt Eingabe als W3C-XML-Schema (Standard) -relaxng : Behandelt Eingabe als RELAX NG (experimentell, nicht unterstützt) -relaxng-compact : Behandelt Eingabe als RELAX NG-Kompaktsyntax (experimentell, nicht unterstützt) -dtd : Behandelt Eingabe als XML DTD (experimentell, nicht unterstützt) -wsdl : Behandelt Eingabe als WSDL und kompiliert enthaltene Schemas (experimentell, nicht unterstützt) -verbose : Verwendet extra-verbose -quiet : Unterdrückt die Compilerausgabe -help : Zeigt diese Hilfemeldung an -version : Zeigt Versionsinformationen an -fullversion : Zeigt vollständige Versionsinformationen an Erweiterungen: -Xinject-code : inject specified Java code fragments into the generated code -Xlocator : enable source location support for generated code -Xsync-methods : generate accessor methods with the 'synchronized' keyword -mark-generated : mark the generated code as @javax.annotation.Generated -episode <FILE> : generate the episode file for separate compilation -Xpropertyaccessors : Use XmlAccessType PROPERTY instead of FIELD for generated classes
Eigentlich ist bis auf die Angabe der Schema-Quelle (aus einer Datei oder alternativ die URL) keine weitere Angabe nötig. Es ist aber praktisch, zwei Optionen zu setzen: -p bestimmt das Java-Paket für die generierten Klassen und -d das Ausgabeverzeichnis, in dem der Generator die erzeugten Dateien ablegt.
Für ein Beispiel wählen wir eine freie Bibel-API. Die URL sieht so aus:
http://api.preachingcentral.com/bible.php?passage=Jn3:16&version=schlachter
Die Angabe von version ist optional, und bestimmt im gesetzten Fall die Sprache bzw. die Bibel-Ausgabe. Die Seite http://www.4-14.org.uk/xml-bible-web-service-api zeigt den Einsatz der einfachen API, bei der ganz simpel in die URL das Buch, Kapitel und Vers steht. Für unser Beispiel sieht das Ergebnis so aus:
<?xml version="1.0" encoding="UTF-8"?> <bible> <title>Jn3:16</title> <range> <request>Jn3:16</request> <result>John 3:16</result> <item> <bookname>John</bookname> <chapter>3</chapter> <verse>16</verse> <text>Denn Gott hat die Welt so geliebt, daß er seinen eingeborenen Sohn gab, damit jeder, der an ihn glaubt, nicht verloren gehe, sondern ewiges Leben habe.</text> </item> </range> <time>0.007</time> </bible>
Für unser Beispiel wollen wir das XML-Dokument nicht von Hand auseinanderpflücken, sondern JAXB soll uns eine gefüllte JavaBean mit allen Informationen liefern. Leider gibt es für dieses einfache XML-Format kein XML-Schema, sodass wir uns mit einem Trick behelfen: wir lassen uns von einem Dienst aus der XML-Datei ein Schema generieren, sodass wir damit zu xjc gehen können. Es gibt im Internet einige freie XML-nach-Schema-Konverter, zum Beispiel http://www.xmlforasp.net/CodeBank/System_Xml_Schema/BuildSchema/BuildXMLSchema.aspx. Das XML-Dokument wird in das Textfeld kopiert und nach dem Knopfdruck „Generate Schema“ folgt:
<?xml version="1.0" encoding="utf-16"?> <xsd:schema attributeFormDefault="unqualified" elementFormDefault="qualified" version="1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="bible"> <xsd:complexType> <xsd:sequence> <xsd:element name="title" type="xsd:string" /> <xsd:element name="range"> <xsd:complexType> <xsd:sequence> <xsd:element name="request" type="xsd:string" /> <xsd:element name="result" type="xsd:string" /> <xsd:element name="item"> <xsd:complexType> <xsd:sequence> <xsd:element name="bookname" type="xsd:string" /> <xsd:element name="chapter" type="xsd:int" /> <xsd:element name="verse" type="xsd:int" /> <xsd:element name="text" type="xsd:string" /> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="time" type="xsd:decimal" /> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema>
Diese Ausgabe speichern wir in der Datei bible.xsd, damit sie von xjc verwendet werden kann.
Den Aufruf von xjc setzen wir in eine Batch-Datei:
PATH=%PATH%;C:\Program Files\Java\jdk-9\bin xjc -d src\main\java -p com.tutego.insel.xml.jaxb.bible bible.xsd
Das Tool generiert alle Typen für den komplexen Typ aus dem XML-Schema, um eine Paket-Annotation festmachen zu können, und ObjectFactory, die einfache Fabrikmethoden enthält, um die gemappten Objekte aufbauen zu können. Die Ausgabe vom Programm ist:
…>xjc -d src\main\java -p com.tutego.insel.xml.jaxb.bible bible.xsd Ein Schema wird geparst ... Ein Schema wird kompiliert ... com\tutego\insel\xml\jaxb\bible\Bible.java com\tutego\insel\xml\jaxb\bible\ObjectFactory.java
TIPP: In Eclipse lässt sich xjc einfach aufrufen. Selektiere im Package-Explorer BIBLE.xsd, im Kontextmenü Generate • JAXB Classes…, dann wähle das Java-Projekt aus, anschließend Next. Im folgenden Dialog gib als Paket „com.tutego.insel.xml.jaxb.bible“ ein, dann klicke auf Finish. Wichtig! Damit xjc gefunden wird, muss das JDK aktiviert sein, nicht das JRE; nur das JDK bringt das Werkzeug xjc mit.
Dann kann ein Java-Programm den Service mit einer URL ansprechen und sich das Ergebnis-XML in eine JavaBean konvertieren lassen.
package com.tutego.insel.xml.jaxb; import javax.xml.bind.JAXB; import com.tutego.insel.xml.jaxb.bible.Bible; public class BibleOnline { public static void main( String[] args ) { String url = "http://api.preachingcentral.com/bible.php?passage=Jn3:16&version=schlachter"; Bible bible = JAXB.unmarshal( url, Bible.class ); System.out.println( bible.getRange().getItem().getText() ); } }
WICHTIG Das JAXB-Modul muss in Java 9 über einen Schalter auf der Kommandozeile hinzugenommen werden: –add-modules java.se.ee.
Leider haben viele XML-Schemas ein Problem, sodass sie nicht direkt vom Schema-Compiler verarbeitet werden können. Ein Beispiel zeigt das Dilemma:
<container> <head><content title="Titel"/></head> <body><content doc="doc.txt"/></body> </container>
In der hierarchischen Struktur heißt das in <head> und <body> vorkommende XML-Element gleich, nämlich content. Die Schema-Datei kann widerspruchslos definieren, dass die beiden XML-Elemente gleich heißen, aber unterschiedliche Attribute erlauben, sozusagen das Head-Content und das Body-Content. Allein durch ihre Hierarchie, also dadurch, dass sie einmal unter head und einmal unter body liegen, sind sie eindeutig bestimmt. Der Schema-Compiler von Java bekommt aber Probleme, da er diese hierarchische Information in eine flache bringt. Er kann einfach eine Klasse Head und Body aufbauen, aber bei <content> steht er vor einem Problem. Da die Schema-Definitionen unterschiedlich sind, müssten zwei verschiedene Java-Klassen unter dem gleichen Namen Content generiert werden. Das geht nicht, und xjc bricht mit Fehlern ab.
Fehler dieser Art gibt es leider häufig, und sie sind der Grund, warum aus vielen Schemas nicht einfach JavaBeans generiert werden können. Erfolglos ohne weitere Einstellungen sind beispielsweise DocBook, Office Open XML, SVG, MathML und weitere. Doch was könnte die Lösung sein? xjc sieht Konfigurationsdateien vor, die das Mapping anpassen können. In diesen Mapping-Dokumenten identifiziert ein XPath-Ausdruck die problematische Stelle und gibt einen Substitutionstyp an. Die Dokumentation auf der JAXB-Homepage weist Interessierte in die richtige Richtung.
Auf der Webseite https://github.com/javaee/jaxb2-commons gibt es eine Reihe nützlicher zusätzlicher Plugins für JAXB. Zu ihnen gehören:
Alle temporalen Typen überschreiben standardmäßig die toString()-Methode und liefern eine standardisierte Ausgabe. Weiterhin lassen sich die temporalen Typen auch bei String.format(…) und dem java.util.Formatter einsetzen.
Daneben bieten die Typen eine format(…)-Methode, der ein Formatierungsobjekt übergeben wird, sodass individuelle Ausgaben nach einem Muster möglich sind.
Klasse | Formatierungsmethode |
LocalDateTime | format(DateTimeFormatter formatter) |
LocalDate | format(DateTimeFormatter formatter) |
LocalTime | format(DateTimeFormatter formatter) |
ZonedDateTime | format(DateTimeFormatter formatter) |
OffsetDateTime | format(DateTimeFormatter formatter) |
Temporale Klassen nutzen den gleiche Parametertyp DateTimeFormatter
Die format(…)-Methode nimmt einen DateTimeFormatter an, der die Ausgabe beschreibt. Wie kommen wir an einen DateTimeFormatter? Die Klasse hat keinen öffentlichen Konstruktor, sondern Konstanten und statische Fabrikmethoden. Über drei Varianten kommen wir zum konkreten Objekt:
Die API-Dokumentation zählt die Konstanten mit ihren Formaten aus, Beispiele sind ISO_LOCAL_DATE oder ISO_ZONED_DATE_TIME. Wichtig zu bedenken ist, dass der temporale Typ die Felder auch haben muss! Ein ISO_ZONED_DATE_TIME ist zum Beispiel bei einem LocalDate nicht möglich.
Praktischer sind die statischen ofLocalizedXXX(…)-Methoden, die eine Aufzählung FormatStyle (FULL, LONG, MEDIUM, SHORT) annehmen:
Einen so aufgebauten DateTimeFormatter lässt sich Zusatzinformation geben mit diversen withXXX(…)-Methoden, etwa withLocale(Locale locale) oder withZone(ZoneId zone).
Beispiel
LocalDateTime now = LocalDateTime.now(); out.println( now.format( BASIC_ISO_DATE ) ); out.println( now.format( ISO_LOCAL_DATE_TIME ) ); out.println( now.format( ofLocalizedDate( SHORT ) ) ); out.println( now.format( ofLocalizedDateTime( MEDIUM ) ) ); out.println( now.format( ofLocalizedDateTime( FULL ).withLocale( FRANCE ) .withZone( ZoneId.of( "America/Cayenne" ) ) ) );
Die Ausgaben sehen so aus:
20170616
2017-06-16T20:39:11.2114379
16.06.17
16.06.2017, 20:39:11
vendredi 16 juin 2017 à 20:39:11 heure de la Guyane française
Eine völlig flexible Ausgabe ermöglicht ein Muster.
Beispiel
LocalDate now = LocalDate.now();
System.out.println( now ); // 2014-03-21
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( „d. MMMM yyyy“ );
String nowAsString = now.format( formatter );
System.out.println( nowAsString ); // 21. März 2014
LocalDate nowAgain = LocalDate.parse( nowAsString, formatter );
System.out.println( nowAgain ); // 2014-03-21
Die API-Dokumentation zu DateTimeFormatter zeigt einige vordefinierte Formatierungsobjekte und listet alle Formatspezifizierer auf.
Neben dem Formatieren bieten die Typen auch eine statische parse(…)-Methode, die einmal mit einem String-Parameter das Format erwartet, was toString() liefert, und eine Version mit zwei Parametern, wobei dann ein Formatierungsobjekt erlaubt ist, um genau das Format anzugeben, nach dem geparst werden soll.
So schöne an Future-Objekten ist die Abarbeitung im Hintergrund und die Möglichkeit, später abzufragen, ob das Ergebnis schon da ist. Allerdings fehlt der Future-Schnittstelle eine Methode, automatisch nach der Fertigstellung einen Folgeauftrag abzuarbeiten. Dafür bietet die Java-Bibliothek eine spezielle Unterklasse CompletableFuture. Die Klasse implementiert die Schnittstelle CompletionStage, die vermutlich die größte Anzahl Operationen in der gesamten Java SE hat. Der Typname drückt aus, das es um die Fertigstellung (engl. completion) von Abschnitten (engl. stage) geht.
Ein Beispiel für einen trinkfesten mutigen Piraten:
package com.tutego.insel.concurrent; import java.time.LocalTime; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; class Pirate { public static void main( String[] arg ) throws Throwable { String result = CompletableFuture.supplyAsync( Pirate::newName ) .thenApply( Pirate::swears ) .thenCombine( drinkRum(), Pirate::combinePiratAndDrinks ) .thenCombine( drinkRum(), Pirate::combinePiratAndDrinks ) .get(); System.out.println( result ); // Pirat Guybrush flucht und trinkt dann 10 Flaschen Rum und trinkt dann 11 Flaschen Rum } static String newName() { Logger.getGlobal().info( "" + Thread.currentThread() ); return "Pirat Guybrush"; } static String swears( String pirate ) { Logger.getGlobal().info( "" + Thread.currentThread() ); return pirate + " flucht"; } static CompletableFuture<Integer> drinkRum() { Logger.getGlobal().info( "" + Thread.currentThread() ); try { TimeUnit.SECONDS.sleep( 1 ); } catch ( Exception e ) { } return CompletableFuture.supplyAsync( () -> LocalTime.now().getSecond() ); } static String combinePiratAndDrinks( String pirat, int bottlesOfRum ) { Logger.getGlobal().info( "" + Thread.currentThread() ); return pirat + " und trinkt dann " + bottlesOfRum + " Flaschen Rum"; } }
Die Ausgabe ist:
Juni 15, 2017 10:41:56 NACHM. com.tutego.insel.thread.concurrent.Pirate drinkRum INFORMATION: Thread[main,5,main] Juni 15, 2017 10:41:56 NACHM. com.tutego.insel.thread.concurrent.Pirate newName INFORMATION: Thread[ForkJoinPool.commonPool-worker-1,5,main] Juni 15, 2017 10:41:56 NACHM. com.tutego.insel.thread.concurrent.Pirate swears INFORMATION: Thread[ForkJoinPool.commonPool-worker-1,5,main] Juni 15, 2017 10:41:57 NACHM. com.tutego.insel.thread.concurrent.Pirate combinePiratAndDrinks INFORMATION: Thread[main,5,main] Juni 15, 2017 10:41:57 NACHM. com.tutego.insel.thread.concurrent.Pirate drinkRum INFORMATION: Thread[main,5,main] Juni 15, 2017 10:41:58 NACHM. com.tutego.insel.thread.concurrent.Pirate combinePiratAndDrinks INFORMATION: Thread[main,5,main] Pirat Guybrush flucht und trinkt dann 57 Flaschen Rum und trinkt dann 58 Flaschen Rum
Zum Programm: Zunächst muss die Kette von Abschnitten aufgebaut werden. Das kann entweder mit dem Standardkonstruktor geschehen, oder mit statischen Methoden. In unserem Fall nutzen wir supplyAsync(Supplier<U> supplier). Die Methode nimmt sich einen freien Thread aus dem ForkJoinPool.commonPool() und lässt den Thread den supplier abarbeiten. Das Ergebnis ist über die Rückgabe, einem CompletableFuture, abrufbar. Als nächsten wenden wir thenApply(Function<? super T,? extends U> fn) an, die vergleichbar ist mit einer map(…)-Operation eines Streams. Interessant wird es bei thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn); sie verbindet das Ergebnis der eigenen CompletionStage über eine Funktion mit einer anderen CompletionStage, die wir in unserem Fall auch wieder mit supplyAsync(…) aufbauen. So kombinieren wir zwei unabhängige CompletionStage miteinander und synchronisieren das Ergebnis. Wir können das gut an der Ausgabe auslesen, dass drinkRum ganz am Anfang schon ausgeführt wird, und zwar vom Thread[main,5,main], nicht vom ForkJoinPool, weil es unabhängig von den anderen läuft.
Sind verfügbar unter http://jdk.java.net/9/.
In Java 9 wurde die Kennung für die Java-Version standardisiert. Wo früher von zum Beispiel 1.9.0_31-b08 die Rede war, heißt es heute 9.1.4+8; die 1. ist also verständig verschwunden. Auch eigene Programme können Versionskennungen nutzen und die Java SE stellt eine Klasse bereit, mit der sich zum Beispiel Versionen vergleichen lassen. Details zu den sogenannten semantischen Versionierung liefert http://semver.org/lang/de/.
Ein Versionskennung hat die Form $MAJOR.$MINOR.$SECURITY. Das Schema besteht aus drei Teilen, die durch Punkte voneinander getrennt sind:
Die Versionen sind rein numerisch und der Gesamtstring matcht auf den regulären Ausdruck [1-9][0-9]*((\.0)*\.[1-9][0-9]*)*. Die Kennung endet nicht mit 0 – so wird $SECURITY weggelassen, wenn sie 0 ist und $MINOR wird nicht gesetzt, wenn $MINOR und $SECURITY beide 0 sind.
Ein Versionsstring ist eine Versionskennung mit einem optionalen Zusatz von Informationen wie einer Vorversion (engl. pre release) oder Build-Informationen. Den Aufbau erklärt die API-Dokumentation an der Klasse Runtime.Version.
Die Runtime-Klasse hat unter Java 9 eine innere Klasse Version bekommen, die so einen Versionsstring aufbauen und parsen kann. Die Version der aktuellen Laufzeitumgebung liefert die statische Methode version().
Beispiel
Gib alle Informationen über den Versionsstring aus:
System.out.println( Runtime.version() ); // 9-ea+159 System.out.println( Runtime.version().major() ); // 9 System.out.println( Runtime.version().minor() ); // 0 System.out.println( Runtime.version().security() ); // 0 System.out.println( Runtime.version().pre() ); // ea System.out.println( Runtime.version().build() ); // 159 System.out.println( Runtime.version().optional() ); // Optional.empty
Eigene Version-Objekte lassen sich aus einem Versionsstring aufbauen mit der einzigen statischen Methode der Klasse Version, und zwar parse(String).
Beispiel
Version version = Version.parse( "9.2.4+45" ); System.out.println( version.version() ); // [9, 2, 4]
An dem Beispiel ist auch version() abzulesen, die eine numerische Liste mit Versionsnummern liefert.
Neben diesen Abfragemethoden kommen weitere Methoden hinzu. Zunächst überschreibt Version die Object-Methoden equals(…), hashCode() und toString(). Und da Version die Schnittstelle Comparable<Runtime.Version> implementiert, hat die Klasse eine Methode compareTo(Runtime.Version), was die Versionsnummern in eine Ordnung bringt. Zusätzlich gibt es compareToIgnoreOptional(Runtime.Version) und equalsIgnoreOptional(Object), was so etwas wie Build-Informationen ignoriert.
Auf den vermutlich 21. September 2017. Aus der E-Mail http://mail.openjdk.java.net/pipermail/jdk9-dev/2017-May/005864.html von Mark Reinhold:
As you probably know by now, the JCP Executive Committee (EC) recently voted [1] not to approve JSR 376, the Java Platform Module System [2], for the next stage of the process.
This vote does not mean that JSR 376 is dead, nor that Jigsaw has been rejected. It only means that the EC raised a number of concerns that it wanted the JSR 376 Expert Group (EG) to address. The JCP rules give the EG thirty days, until 7 June, to submit a revised specification for a second EC vote, which will end no later than 26 June [3].
The JSR 376 EG held a series of conference calls over the past two weeks in order discuss the EC’s concerns [4]. The net impact of those meetings on JDK 9 itself was to clarify the specification of the module system’s resolution algorithm, work on which had already begun, and to add one five-line method to the module-system API. These changes, together with additional clarifications to the JSR 376 and JSR 379 (Java SE 9) [5] Specifications, will hopefully address the EC’s concerns.
In order to be ready for all possible outcomes I suggest that here in the JDK 9 Project we continue to work toward the current goal of producing an initial Release Candidate build on 22 June [6], but adjust the GA date in order to accommodate the additional time required to move through the JCP process. To be specific, I propose that we move the GA date out by eight weeks, from 27 July to 21 September.
Comments on this proposal from JDK 9 Committers are welcome, as are reasoned objections. If no such objections are raised by 23:00 UTC next Tuesday, 6 June, or if they’re raised and satisfactorily answered, then per the JEP 2.0 process proposal [7] this will be the new schedule for JDK 9.