Java 11 steht seit Ende September 2018 zum Download bereit und stellt nach Java 8 wieder ein sogenanntes LTS-Release (Long Time Support) dar. Das ist insofern erfreulich, da hier einige Jahre Support und Updates angeboten werden, wohingegen Java 9 und 10 durch die neue Release-Politik von Oracle jeweils lediglich für eine kurze Zeitspanne von 6 Monaten aktuell waren und auch keine Updates mehr erhalten. In seiner neuen Serie zu Java 11 gibt Michael Inden einen Einblick in die Features der neuen Java-Version.
Michael Inden hat bereits die Highlights von Java 9 in seiner Artikelserie Java 9 – Eine Einführung vorgestellt, in der Serie Best of Java 10 ging er auf die Neuerungen in Java 10 ein. Die vorliegende Artikelserie setzt sich aus Texten zusammen, die auch Teil seines neuen Buches zu Java 10/11 sein werden, das beim dpunkt.verlag erscheinen wird.
Im vorigen Serienteil habe ich eine kleinere Erweiterung in der Klasse java.util.Optional<T>
vorgestellt. Eine solche findet sich auch in den Klassen java.util.function.Predicate<T>
sowie in java.util.concurrent.Time-Unit
. Beide Neuerungen behandlte ich in separaten Abschnitten. In diesem Teil stelle ich das HTTP/2-API vor, das mit Java 11 den Incubator-Status verlassen hat und zum vollwertigen API in das JDK aufgenommen wurde. Im nächsten Teil geht es dann abschließend um die Neuerungen in der JVM sowie um die Deprecations in Java 11.
Bis einschließlich Java 8 war eine HTTP-Kommunikation mühselig und wenig intuitiv implementierbar, da nur eine Low-Level-Abstraktion existierte: Dies musste mithilfe der Klassen java.net.URL
und java.net.HttpURLConnection
sowie java.io.Input-/OutputStream
selbst ausprogrammiert werden. In Java 9 ist ein neues HTTP/2-API enthalten. Da es jedoch bis zum Release-Datum von JDK 9 nicht vollständig fertiggestellt werden konnte, befindet es sich dort in einem sogenannten Incubator-Status, ist aber kein richtiger Bestandteil des JDKs.
Mit Java 11 wurde das HTTP/2-API vollwertig in den Packages java.net.HTTP.*
ins JDK integriert und hat gegenüber der Version aus Java 9 ein leichtes Facelifting erfahren. Wie schon in Java 9 orientiert sich das Programmiermodell an den Bestandteilen einer HTTP-Kommunikation also etwa Request und Response. Diese an der Fachlichkeit ausgerichtete Terminologie erleichtert das Verständnis. Erwähnenswert ist weiterhin, dass die Verarbeitung des Bodys der Antwort mithilfe eines sogenannten BodyHandlers geschieht. Schauen wir auf ein einführendes Beispiel.
Ein Zugriff auf die Seite von Oracle und das Aufbereiten des Inhalts als String geschieht mit dem HTTP-API aus JDK 11 lesbar und verständlich wie folgt:
private static void readOraclePageJdk9() throws URISyntaxException,
IOException,
InterruptedException
{
final URI uri = new URI("https://www.oracle.com/index.html");
final HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
final BodyHandler<string> asString = HttpResponse.BodyHandlers.ofString();
final HttpClient httpClient = HttpClient.newHttpClient();
final HttpResponse<string> response = httpClient.send(request, asString);
printResponseInfo(response);
}
private static void printResponseInfo(final HttpResponse<string> response)
{
final int responseCode = response.statusCode();
final String responseBody = response.body();
final HttpHeaders headers = response.headers();
System.out.println("Status: " + responseCode);
System.out.println("Body: " + responseBody);
System.out.println("Headers: " + headers.map());
}
</string></string></string>
Ein java.net.http.HttpRequest
wird durch Methoden wie newBuilder(URL)
, GET()
oder POST()
erzeugt. Zur Verarbeitung der Antwortdaten ist dann eine Instanz eines java.net.http.BodyHandlers
zuständig. Davon sind einige vordefiniert, die über die Factory-Klasse der inneren Klasse BodyHandlers erzeugt werden, hier mit ofString()
. Ausgangsbasis für eine HTTP-Kommunikation ist eine Instanz vom Typ java.net.http.HttpClient
, die mit der Fabrikmethode newHttpClient()
erzeugt wird. Dann kann die Kommunikation synchron durch einen Aufruf von send()
gestartet werden – auf eine asynchrone Verarbeitung gehe ich später ein.
Die zurückgelieferte java.net.http.HttpResponse
bietet vor allem die beiden Methoden statusCode()
und body()
. Erstere ermöglicht Zugriff auf den HTTPStatuscode und zweite erlaubt es, den Inhalt der Webseite auszulesen. Zudem liefert die Methode headers()
ein java.net.http.HttpHeaders
-Objekt, das Informationen zu den Header-Werten der Antwort bereitstellt.
Alternativ zu der im Beispiel genutzten Stringrepräsentation kann ein BodyHandler unter anderem mit den Methoden ofLines()
, ofByteArray()
, ofFile()
und ofFileDownload()
in der durch den Namen beschriebenen Ergebnisform erzeugt werden.
Mit dem gerade kennengelernten BodyHandler namens ofFile()
wollen wir noch etwas weitergehen und die zuvor als String ausgelesene Oracle-Webseite nun als Datei herunterladen:
public static void main(final String[] args) throws Exception
{
var uri = new URI("https://www.oracle.com/index.html");
var request = HttpRequest.newBuilder(uri).GET().build();
var downloadPath = Paths.get("oracle-index.html");
var asFile = HttpResponse.BodyHandlers.ofFile(downloadPath);
var httpClient = HttpClient.newHttpClient();
var response = httpClient.send(request, asFile);
if (response.statusCode() == 200)
{
System.out.println("Content written to file: " +
downloadPath.toAbsolutePath());
System.out.println(response.statusCode());
System.out.println(response.body());
}
// wait for completion
Thread.sleep(1000);
}
Startet man das obige Programm, so erhält man in etwa folgende Ausgaben, die zeigen, dass ein Download erfolgt ist:
Content written to file: /Users/michael.inden/eclipse-workspace/Java11Examples/
oracle-index.html
200
oracle-index.html
Vor allem für länger dauernde Berechnungen oder I/O-Operationen ist es sinnvoll, diese in einem separaten Thread asynchron auszuführen, um Blockierungen, wie sie durch eine synchrone Verarbeitung auftreten, zu vermeiden. Mit dem neuen HTTP/2-API ist diese Anforderung kinderleicht umzusetzen. Praktischerweise stimmen dabei die meisten Schritte bei der asynchronen HTTP-Kommunikation mit denen der synchronen überein. Man muss lediglich statt der Methode send()
die Methode sendAsync()
aufrufen, um eine asynchrone Kommunikation zu erzielen.
Weil das Ergebnis noch nicht direkt verfügbar ist, wird zur weiteren Verarbeitung ein CompletableFuture<HttpResponse>
zurückgeliefert. Mit diesem kann man auf die Fertigstellung warten und über isDone()
abfragen, ob die Verarbeitung durchgeführt wurde. Für den Fall, dass dies so ist, kann man dann über die Methode get()
auf die im CompletableFuture<HttpResponse>
vorliegende HttpResponse zugreifen. Die Parallelverarbeitung und das Warten auf die Fertigstellung sind im Listing durch eine Methode waitForCompletion()
angedeutet. Sollen keine Aktionen für den Time-out erfolgen, so kann man die Verarbeitung auch einfacher durch Aufruf von thenAccept(Consumer<? super T>)
ausdrücken:
public static void main(final String[] args) throws Exception
{
var uri = new URI("https://www.oracle.com/index.html");
var request = HttpRequest.newBuilder(uri).GET().build();
var asString = HttpResponse.BodyHandlers.ofString();
var httpClient = HttpClient.newHttpClient();
final CompletableFuture<httpresponse<string>> asyncResponse = httpClient.sendAsync(request, asString);
// Variante 1: Verarbeitung, sobald die Response eintrifft
asyncResponse.thenAccept(response -> printResponseInfo(response));
// Variante 2: Verarbeitung mit eigenem Warten, Abfrage auf Erfolg und
// bei nicht erfolgreichem Abschluss mit einem Abbruch per cancel(true)
waitForCompletion();
if (asyncResponse.isDone())
{
final HttpResponse<string> response = asyncResponse.get();
printResponseInfo(response);
}
else
{
asyncResponse.cancel(true);
System.err.println("timeout");
}
}
</string></httpresponse<string>
Folgende Hilfsmethoden kommen im obigen Listing zum Einsatz:
private static void waitForCompletion() throws InterruptedException
{
for (int i = 0; i < 10; i++)
{
System.out.println("Step " + i);
Thread.sleep(200);
}
}
private static void printResponseInfo(final HttpResponse<string> response)
{
var responseCode = response.statusCode();
var responseBody = response.body();
System.out.println("Status: " + responseCode);
System.out.println("Body: " + responseBody);
}
</string>
Die Unterstützung von HTTP/2 ist eine sinnvolle Ergänzung, die zudem eine Rückwärtskompatibilität zu HTTP 1.1 bietet. Nebenbei erhält man Geschwindigkeitssteigerungen von bis zu 50 % sowie ein verständliches API. Auch die Möglichkeit asynchrone Aufrufe auszuführen, ist hervorzuheben, da sich diese durch den Einsatz von CompletableFuture<T>
in der Programmierung sehr einfach gestalten.
Im kommenden und abschließenden Teil dieser Artikelserie zu Java 11 geht es dann um die Neuerungen in der JVM und die Deprecations der aktuellen Sprachversion!
Michael Indens Buch bietet eine umfassende Einführung in die professionelle Java-Entwicklung und vermittelt Ihnen das notwendige Wissen, um stabile und erweiterbare Softwaresysteme auf Java-SE-Basis zu bauen. Praxisnahe Beispiele helfen dabei, das Gelernte rasch umzusetzen. Neben der Praxis wird viel Wert auf das Verständnis zugrunde liegender Konzepte gelegt.
Die Neuauflage wurde durchgehend überarbeitet, aktualisiert und erweitert. Auch Java 9 sind zwei Kapitel gewidmet. Eine neue Auflage des Buches mit Erweiterungen für aktuellere Java-Versionen ist für den Sommer bzw. Herbst 2019 geplant.